📖📖📖快起床学习啦!你怎么睡的着哒
目录
1. 6个默成员函数介绍
🥙🥙在c++中,我们定义了一个类,编译器会默认在这个类中生成6个成员函数,空类也不例外。成员函数如下图。当我们实例化对象时调用构造函数进行对象初始化,销毁对象时调用析构函数进行资源释放,用对象创建另一个相同的对象时调用拷贝构造函数,将对象的值赋值给另一个对象时调用赋值运算符重载之后的函数,取地址时调用&函数。
🥪🥪这几个函数保证了类和对象的基本操作,这6个函数如果我们不定义,编译器会自动生成。但是,笔者建议在新手期除取地址运算符外,其余都在类中自己定义,因为编译器生成的构造默认函数有可能不能满足我们需求,而出现错误。
c++语法是很灵活的,他将所有操作的实现权力都交给了用户,比如赋值运算符的重载在Java中就是不支持的。😎😎
2.构造函数
🍗🍗🍗2.1概念
构造函数是类的一个特殊的成员函数,名字与类名相同,无返回值,它保证了每个数据成员都有一个合适的初始值,并在对象的生命周期只调用一次。名字虽然叫构造函数,实际上是为了初始化对象而存在的,并不是开辟空间。🐸🐸
特性:函数名与类名相同;由编译器调用,用户不能调用;无返回值;可以重载(拷贝构造函数就是重载的一种)
🍗🍗🍗2.2构造函数举例
#include<iostream> 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<iostream> 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<iostream> 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<iostream> 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<iostream> 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<iostream> #include<cstring> 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<iostream> 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<iostream> 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<iostream> 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<iostream> 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<iostream> using namespace std; class student { private: string name; int age; string num; public: student() {} //无参构造函数 student* operator&(){ cout << this<<endl; cout << "我是&" << endl; return this; } }; int main() { student stu; &stu; return 0; }
👼👼结论:这个和const &一般情况下,程序员不需要重载。
7.初始化列表
🍯🍯🍯7.1概念
与其它函数不同,构造函数可以有初始化列表,初始化列表以冒号开头,后跟一系列以逗号分隔的初始化字段。
🍯🍯🍯7.2代码演示
#include<iostream> 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<iostream> 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们,今天的内容就到这里啦,咱们下次见!🍻🍻🍻