13天带你了解C++ ---DAY5 c++类中的6个默认成员函数

13天带你了解C++ ---DAY5 c++类中的6个默认成员函数,第1张

📖📖📖快起床学习啦!你怎么睡的着哒

目录

1. 6个默成员函数介绍

2.构造函数

 🍗🍗🍗2.1概念

🍗🍗🍗2.2构造函数举例

🍗🍗🍗2.3易错点

🍗🍗🍗 2.4举一反三:类中包含其它类对象

🍗🍗🍗2.5扩展:c++11新特性:在成员变量中赋值

🍗🍗🍗2.6补充:全缺省构造函数

3.析构函数

 🍔🍔🍔3.1析构函数概念 

🍔🍔🍔3.2析构函数特性

🍔🍔🍔3.3析构函数举例

🍔🍔🍔3.4验证编译器自动调用析构函数

🍔🍔🍔3.4易错点(注意啦!!!)

4.拷贝构造函数

🍙🍙🍙4.1概念

🍙🍙🍙4.2拷贝构造函数举例

🍙🍙🍙4.3易错点:传值还是传引用?

5.运算符重载

🍨🍨🍨5.1概念        

🍨🍨🍨5.2特征

🍨🍨🍨5.3重载格式

🍨🍨🍨5.4运算符重载举例

              5.4.1  =运算符重载

              5.4.2  连续赋值运算符重载

               5.4.3 ++运算符重载

6.& *** 作符重载

7.初始化列表

🍯🍯🍯7.1概念

🍯🍯🍯7.2代码演示

8.static

🍟🍟🍟8.1概念

🍟🍟🍟8.2特征

🍟🍟🍟8.3代码验证


1. 6个默成员函数介绍

        🥙🥙在c++中,我们定义了一个类,编译器会默认在这个类中生成6个成员函数,空类也不例外。


成员函数如下图。


当我们实例化对象时调用构造函数进行对象初始化,销毁对象时调用析构函数进行资源释放,用对象创建另一个相同的对象时调用拷贝构造函数,将对象的值赋值给另一个对象时调用赋值运算符重载之后的函数,取地址时调用&函数。


        🥪🥪这几个函数保证了类和对象的基本 *** 作,这6个函数如果我们不定义,编译器会自动生成。


但是,笔者建议在新手期除取地址运算符外,其余都在类中自己定义,因为编译器生成的构造默认函数有可能不能满足我们需求,而出现错误。


        c++语法是很灵活的,他将所有 *** 作的实现权力都交给了用户,比如赋值运算符的重载在Java中就是不支持的。


😎😎

2.构造函数
 🍗🍗🍗2.1概念

        构造函数是类的一个特殊的成员函数,名字与类名相同,无返回值,它保证了每个数据成员都有一个合适的初始值,并在对象的生命周期只调用一次。


名字虽然叫构造函数,实际上是为了初始化对象而存在的,并不是开辟空间。


🐸🐸

        特性:函数名与类名相同;由编译器调用,用户不能调用;无返回值;可以重载(拷贝构造函数就是重载的一种)

🍗🍗🍗2.2构造函数举例
#include
using namespace std;
class student{
private:
	string name;
	int age;
	string num;
public:
	student() {
		//无参构造函数
	}

	student(string _name,int _age,string _num) {
		//带参构造函数
		 name = _name;
		 age = _age;
		 num = _num;
	}

};


int main() {
	student stu1;
	student stu2("大米",20,"41909310221");
	return 0;
}

       👼👼 结论:以上分别调用了无参构造函数和带参构造函数进行实例化。


如果用户未显式定义构造函数,那么 *** 作系统默认输出无参构造函数。


🍗🍗🍗2.3易错点
#include
using namespace std;

class student{
private:
	string name;
	int age;
	string num;
 
public:
	student(string _name,int _age,string _num) {
		//带参构造函数
		 name = _name;
		 age = _age;
		 num = _num;
	}

};


int main() {
	student stu1;
	student stu2("大米",20,"41909310221");
	return 0;
}

         👼👼结论:用户一旦显式定义构造函数,编译器就不再生成默认无参构造函数。


此处只定义带参构造函数,编译时告诉我们缺少无参构造函数。


🍗🍗🍗 2.4举一反三:类中包含其它类对象
#include
using namespace std;

class homework {
public:
	void hom() {
		cout << "做作业" << endl;
	}
};

class student{
 public:
	student() {
		//无参构造函数
		homework h1;  //实例化homework类的对象
	}
};


int main() {
	student stu1;
	return 0;
}

         👼👼结论:此处homework类未定义构造函数,编译器生成了默认无参构造函数,使得代码通过编译。


🍗🍗🍗2.5扩展:c++11新特性:在成员变量中赋值
#include
using namespace std;
class student {
private:
	string name = "我是无参构造函数";
	int age = 20;
	string num = "41909310221";
public:
	student() {
		//无参构造函数
	}

	student(string _name, int _age, string _num) {
		//带参构造函数
		name = _name;
		age = _age;
		num = _num;
	}

};

int main() {
	student stu1;
	student stu2("我是含参构造函数", 20, "41909310221");
	return 0;
}

         👼👼结论:c++11中可以直接在成员变量中赋值,含参构造函数参数传递值会覆盖在前半句中的值。


🍗🍗🍗2.6补充:全缺省构造函数
#include
using namespace std;
class student {
private:
	string name;
	int age;
	string num ;
public:

	student(string _name="dami", int _age=20, string _num="41909310221") {
		//全缺省构造函数
		name = _name;
		age = _age;
		num = _num;
	}

};

int main() {
	student stu1;     //上边未定义无参构造函数,但是能通过编译喔,详情请看解释!!!
	student stu2("大米", 20, "41909310221");
	return 0;
}

         👼👼结论:全缺省参数构造函数是给参数列表成员一个初始值,若传递的是带参的,则覆盖这个初始值,若是无参的,则使用这个初始值。


换句话说,全缺省参数构造函数既可以当无参构造函数用,又可以当做有参构造函数使用。


3.析构函数
 🍔🍔🍔3.1析构函数概念 

         析构函数与构造函数功能相反,析构函数是完成对象的销毁,局部对象销毁工作是由编译器完成的。


假如对象所在函数已经执行完毕,编译器会自动调用析构函数。


与构造函数不同的是析构函数可以由用户进行调用。


         定义形式:C++规定析构函数的名字是类名的前面加一个波浪号(~)。


~类名(){
    函数体
}
🍔🍔🍔3.2析构函数特性

        特性:不能有返回值;不能带参数,所以不能重载;完成对象中资源的清理工作;函数名前加~;生命周期结束时,编译器自动调用。


🍔🍔🍔3.3析构函数举例
#define _CRT_SECURE_NO_WARNINGS
#include
#include
using namespace std;

class test2 {
private:
	char* p=nullptr;
public:
	test2(const char* _str) {
		p = (char*)malloc(20);
		strcpy(p,_str);
		cout << _str << endl;
	}

	~test2() {
		if (p != nullptr) {
			free(p);
			p = nullptr;
			cout << "调用自定义析构函数" << endl;
}
	}
};

int main() {
	test2 t2("构造函数");
    t2.~test2();
	return 0;
}

         👼👼结论:上边我们自定义了析构函数,也显式调用了,同时我们在重载时让它打印了一句话。


当我们有特殊需求时,我们也可以重加入我们想要的功能。


注意用户显式调用时,只能当作普通成员函数调用,并不会释放栈内存。


🍔🍔🍔3.4验证编译器自动调用析构函数
int main() {
	test2 t2("构造函数");
	return 0;
}

        👼👼结论:还是上边的代码,但是我们没有显式调用,编译器也调用了我们定义的析构函数,证明,编译器会在对象生命周期结束时自动调用析构函数。


🍔🍔🍔3.4易错点(注意啦!!!)

        不管用户有没有调用析构函数,编译器都会在对象脱离其作用域时调用析构函数,这个析构函数若用户定义了则调用定义好的,若未定义,则编译器默认生成一个析构函数然后调用。


     🍻🍻既然编译器会生成,为什么我们还要定义呢?

        因为编译器生成的析构函数,只能释放栈空间,没法释放我们在堆上申请的空间,所以如果我们不在析构函数中手动释放堆上内存,势必会内存泄漏。


下边定义的析构函数,手动释放了堆空间,可供参考。


~test2() {
		if (p != nullptr) {
			free(p);
			p = nullptr;
			cout << "调用自定义析构函数" << endl;
}
4.拷贝构造函数
🍙🍙🍙4.1概念

对于内置类型的数据来说相互赋值是很简单的,如下

int a=10;
int b=a;   //直接可以赋值

student stu1;
student stu2;
stu1=stu2;  //达咩!! 
/*
     因为对象有很多成员变量,编译器比知道stu1中哪个成员变量对应stu2中哪个,
  所以我们对构造函数重载,让它可以直接赋值,这就是拷贝构造函数
*/
🍙🍙🍙4.2拷贝构造函数举例
#include
using namespace std;


class student {
private:
	string name;
	int age;
	string num ;
public:
	
	student(string _name, int _age, string _num) {
		name = _name;
		age = _age;
		num = _num;
	}

	student(student& stu) {  //拷贝构造函数
		name = stu.name;
		age = stu.age;
		num = stu.num;
	}
};

int main() {	
	student stu2("dami", 20, "41909310221");
	student stu1(stu2);
	return 0;
}

🍙🍙🍙4.3易错点:传值还是传引用?

        在对构造函数重载时,我们需要在参数列表传递一个对象进去,所以我们有三种思路,直接传值,传地址,传引用。


即:

student(student& stu) {  //传引用
		name = stu.name;
		age = stu.age;
		num = stu.num;
	}
	
	student(student  stu) {  //直接把对象值传进去,实际上编译是会报错的,所以不能用
		name = stu.name;
		age = stu.age;
		num = stu.num;
	}
	
	student(student* stu) {   //传递对象的地址
		name = stu->name;
		age = stu->age;
		num = stu->num;
	}

        👼👼结论:拷贝构造函数最好传递引用,虽然传递指针也可以达到效果,但是指针出错风险大,所以引用。


        分析:

                直接传对象值为什么会报错呢?

                int b=a其实也使用了内置的拷贝构造函数,先调用拷贝构造函数将a的值拷贝一份放在一个临时变量中,然后再调用拷贝构造函数将临时变量的值赋给b。


对象也是同理,编译器需要先调用拷贝构造函数将对象stu1的值拷贝一份放在一个临时对象中,但是这个阶段我们在重载拷贝构造函数,所以对象的拷贝构造函数不能使用。


我们只能抛弃这这种传值的思路。


5.运算符重载
🍨🍨🍨5.1概念        

        既然上边这些都可以重载,那么想像+ - * / 这些运算符当然也可以重载,c++中只有下边的运算符不可以重载。


🍨🍨🍨5.2特征

        具有返回值类型,函数名以及参数列表,返回值和参数列表和普通函数的差不多。


🍨🍨🍨5.3重载格式

        函数名:关键字operator后加需要重载的运算符符号。


student&  operator=(student& stu){}    //对赋值运算符=重载
student&  operator+(student& stu1,student& stu2){}    //对运算符+重载
🍨🍨🍨5.4运算符重载举例       5.4.1  =运算符重载
#include
using namespace std;

class student {
private:
	string name;
	int age;
	string num;
public:	
	student(string _name, int _age, string _num) {
		name = _name;
		age = _age;
		num = _num;
	}
	student& operator=(const student& stu) {
		name = stu.name;
		age = stu.age;
		num = stu.num;
	}
};

int main() {	
	student stu2("dami", 20, "41909310221");
	student stu1=stu2;
	return 0;
}

     

5.4.2  连续赋值运算符重载
#include
using namespace std;

class student {
private:
	string name;
	int age;
	string num;
public:
	student() {}		//无参构造函数

	student(string _name, int _age, string _num) {	//有参构造函数
		name = _name;
		age = _age;
		num = _num;
	}

	student& operator=(const student& stu) {	//赋值运算符重载
		name = stu.name;
		age = stu.age;
		num = stu.num;
		return *this;        //重点!!!!
	}
};

int main() {
	student stu2("dami", 20, "41909310221");
	student	stu1;
	student stu3 = stu1 = stu2;
	return 0;
}

👼👼结论:在第一次=调用结束后,返回*this,即stu1,再用stu1对stu3赋值。


5.4.3 ++运算符重载

        内置变量可以使用i++和++i ,对象也是可以的

#include
using namespace std;

class student {
private:
	string name;
	int age;
	string num;
public:
	student() {}		//无参构造函数

	student(string _name, int _age, string _num) {	//有参构造函数
		name = _name;
		age = _age;
		num = _num;
	}

	student& operator++() {
		age += 1;			//前置++
		return *this;
	}

	student& operator++(int) {
		student tmp(*this); //后置++
		age += 1;
		return tmp;
	}
};

int main() {
	student stu2("dami", 20, "41909310221");
	++stu2;				//!!!此处我们的++,默认是给年纪++,具体需求可以自己定义
	return 0;
}

   

 分析:

        前置++在对象中的数值+后,返回对象,后置++在对象+后,创建一个临时对象,使用编译器生成的默认拷贝构造函数,为临时对象tmp赋值。


返回临时对象,实际上对象也达到+的效果。


6.& *** 作符重载

&运算符一般是不用程序员重载的,不过在设计上是可以重载的,比如下边的栗子

#include
using namespace std;

class student {
private:
	string name;
	int age;
	string num;
public:
	student() {}		//无参构造函数

	student* operator&(){
		cout << this<

👼👼结论:这个和const &一般情况下,程序员不需要重载。


7.初始化列表
🍯🍯🍯7.1概念

        与其它函数不同,构造函数可以有初始化列表,初始化列表以冒号开头,后跟一系列以逗号分隔的初始化字段。


🍯🍯🍯7.2代码演示
#include
using namespace std;

class student {
private:
	string name;
	int age;
	string num;
public:
	student()
		:name("dami")
		,age(20)
		,num("419")
	{
	}		//无参构造函数
};

int main() {
	student stu;
	return 0;
}

👼👼结论:初始化列表建议总是带上,这样能够提高效率。


8.static
🍟🍟🍟8.1概念

        声明为static类成员称为类的静态成员,用static声明静态成员变量,称为静态成员变量。


用static修饰的成员函数称为静态成员函数。


静态成员必须在类外单独定义和初始化。


🍟🍟🍟8.2特征

        静态成员变量在类中之时声明,需要在类外单独定义,定义时不需要加static关键字

        静态成员变量并不在具体的对象中,是所有对象共享的,不会影响对象大小

        静态成员变量访问 类名::静态成员变量名     对象.静态成员变量名

🍟🍟🍟8.3代码验证
#include
using namespace std;

class student {
	
public:
	static int count;	//静态成员变量声明
	student()
	{
		count++;
	}		
};

int student:: count=0;	//静态成员变量定义

int main() {
	student stu,stu1,stu2;
	cout << sizeof(stu) << endl;
	cout << student::count << endl;
	return 0;
}

👼👼 结论:count是所有对象共享的,且不影响对象大小

uu们,今天的内容就到这里啦,咱们下次见!🍻🍻🍻

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/607007.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-14
下一篇 2022-04-14

发表评论

登录后才能评论

评论列表(0条)