1.对象的初始化和清理
● C++的每个对象都会有初始设置,以及对象销毁前的清理数据的设置。。
——一个对象或者变量没有初始状态,对其使用后果是未知的,使用完后,没有及时清理,也会造成一定的安全问题
C++采用构造函数和析构函数解决上述问题,这俩个函数会被自动调用,完成对象的初始化和清理工作。
● 构造函数:创建对象时为对象的成员属性赋值(自动调用)
● 析构函数:对象销毁前系统自动调用,执行一些清理工作。
构造函数语法:类名(){}
1.构造函数,没有返回值也不写void
2.函数名称与类名相同
3.构造函数可以有参数,因此可以发生重载
4.程序在调用对象时会自动调用构造,无需手动调用,而且只会调用一次
析构函数语法:~类名(){}
1.析构函数,没有返回值也不写void
2.函数名称与类名相同,在名称前加上符号~
3.析构函数不可以有参数,因此不能发生重载
4.程序在对象销毁前会自动调用析构,无需手动调用,而且只会调用一次
例子:
1 #include <iostream> 2 3 using namespace std; 4 5 class Person{ 6 public: 7 //构造函数: 8 //如果不写,编译器会自动写一句这样的↓: 9 //Person(){} //空实现 10 Person(){ 11 cout << "Person构造函数的调用" << endl; 12 } 13 //析构函数: 14 //如果不写,编译器会自动写一句这样的↓: 15 //~Person(){} //空实现 16 ~Person(){ 17 cout << "Person析构函数的调用" << endl; 18 } 19 }; 20 21 void test01(){ 22 Person p; //栈区的数据,在test01执行完后,就被释放 23 } 24 25 int main(){ 26 test01(); 27 system("pause"); 28 return 0; 29 }
如果创建对象在main函数中,则会:
1 #include <iostream> 2 3 using namespace std; 4 5 class Person{ 6 public: 7 //构造函数: 8 //如果不写,编译器会自动写一句这样的↓: 9 //Person(){} //空实现 10 Person(){ 11 cout << "Person构造函数的调用" << endl; 12 } 13 //析构函数: 14 //如果不写,编译器会自动写一句这样的↓: 15 //~Person(){} //空实现 16 ~Person(){ 17 cout << "Person析构函数的调用" << endl; 18 } 19 }; 20 21 // void test01(){ 22 // Person p; //栈区的数据,在test01执行完后,就被释放 23 // } 24 25 int main(){ 26 // test01(); 27 Person p; 28 system("pause"); 29 return 0; 30 }
注意区分两者不同的区别。。。
2.构造函数的分类和调用
有两种分类方式,三种调用方法:
分类:
按参数分为:有参构造和无参构造
按类型分为:普通构造和拷贝构造
调用:
括号法
显示法
隐式转换法
例子:
1 #include <iostream> 2 3 using namespace std; 4 5 class Person{ //分类 6 public: 7 int age; 8 //构造函数 9 Person(){} //无参构造(默认构造) 10 Person(int a){ 11 age = a; 12 } //有参构造 13 14 //拷贝构造函数 15 Person(const Person &p){ 16 age = p.age; //此处相当于执行这段代码,写不写无所谓 17 } //把传入的对象的所有属性,拷贝到此函数中 18 19 ~Person(){} 20 }; 21 22 //调用 23 void test01(){ 24 //括号法 25 Person p1; //默认构造函数的调用 26 Person p2(10); //有参构造函数的调用 27 Person p3(p2); //拷贝构造函数的调用,相当于把p2的所有属性复制了一份,并且重新命名为p3 28 //注意事项1:调用默认构造函数时,不要加(),例如:Person pp(),编译器会认为这是一个函数声明,而不是创造对象。。 29 30 //显示法 31 Person pp1; 32 Person pp2 = Person(10); //有参构造,这里相当于pp2成为了Person(10)的对象名 33 Person(10); //匿名对象 特定:当前行执行完毕后,系统会立即回收掉匿名对象(即马上调用析构函数,再去执行下一步) 34 Person pp3 = Person(p2); //拷贝构造 35 //注意事项2:不要利用拷贝构造函数去初始化匿名对象 36 //例如: 37 //Person p; 38 //Person(p); //此时编译器会认为Person p等价于Person(p),即产生了重定义 39 40 //隐式转化法 41 Person p4 = 10; //相当于Person p4 = Person(10); 42 Person p5 = p4; //拷贝构造 43 } 44 45 int main(){ 46 47 system("pause"); 48 return 0; 49 }构造函数和析构函数
注意事项:
①.调用默认构造函数时,不要加(),例如:Person pp(),编译器会认为这是一个函数声明,而不是创造对象。。
②.不要利用拷贝构造函数去初始化匿名对象。。。
3.拷贝构造函数调用的3个情况
● 使用一个已经创建完毕的对象来初始化一个新对象
● 值传递的方式给函数参数传值
● 以值方式返回局部对象
例子:
1 #include <iostream> 2 3 using namespace std; 4 5 class Person{ 6 public: 7 int mAge; 8 9 Person(){ 10 cout << "调用了默认构造函数" << endl; 11 } 12 13 Person(int age){ 14 mAge = age; 15 cout << "调用了有参构造函数" << endl; 16 } 17 18 Person(const Person &p){ 19 mAge = p.mAge; 20 cout << "调用了拷贝构造函数" << endl; 21 } 22 23 ~Person(){ 24 cout << "调用了析构函数" << endl; 25 } 26 }; 27 28 //1.使用一个已经创建完毕的对象来初始化一个新对象 29 void test01(){ 30 Person p1(20); 31 Person p2(p1); 32 cout << "P2的年龄为:" << p2.mAge << endl; 33 } 34 35 //2.值传递的方式给函数参数传值 36 void doWork(Person p){ 37 38 } 39 void test02(){ 40 Person p; 41 doWork(p); 42 } 43 44 //3.以值方式返回局部对象 45 Person doWork2(){ //返回什么值取决于函数的类型,需要返回类,则函数要是类型 46 Person p1; 47 cout << (int*)&p1 << endl; 48 return p1; 49 } 50 void test03(){ 51 Person p = doWork2(); 52 cout << (int*)&p << endl; 53 } 54 55 int main(){ 56 test01(); //每次运行一个,不需要的请注释掉 57 test02(); 58 test03(); 59 system("pause"); 60 return 0; 61 }
第一点:
第二点:
第三点:
4.构造函数调用规则
默认情况下,C++编译器至少会给一个类添加3个函数:
①默认构造函数(无参空函数体)
②默认析构函数(无参空函数体)
③默认拷贝构造函数,对属性进行值拷贝
规则:
● 若用户定义了有参构造函数,C++不再提供①,但是会提供③
● 若用户定义了拷贝构造函数,C++不再提供其他任何构造函数
5.深拷贝和浅拷贝
浅拷贝:赋值拷贝
深拷贝:在堆区重新申请空间,再进行拷贝
例子:
浅拷贝:
1 #include <iostream> 2 3 using namespace std; 4 5 class Person{ 6 public: 7 Person(){ 8 cout << "默认构造函数调用" << endl; 9 } 10 Person(int age){ 11 mAge = age; 12 cout << "有参构造函数调用" << endl; 13 } 14 ~Person(){ 15 cout << "析构函数调用" << endl; 16 } 17 int mAge; 18 }; 19 20 void test01(){ 21 Person p1(18); 22 cout << "P1年龄:" << p1.mAge << endl; 23 Person p2(p1); //浅拷贝 24 cout << "P2年龄:" << p2.mAge << endl; 25 } 26 int main(){ 27 test01(); 28 system("pause"); 29 return 0; 30 }
深拷贝(堆区规则:先进后出):
会出现堆区内存重复释放的问题代码:
1 #include <iostream> 2 3 using namespace std; 4 5 class Person{ 6 public: 7 Person(){ 8 cout << "默认构造函数调用" << endl; 9 } 10 Person(int age, int height){ 11 mAge = age; 12 mHeight = new int(height); //指针接收堆区数据 13 cout << "有参构造函数调用" << endl; 14 } 15 ~Person(){ 16 //把堆区开辟的数据释放 17 if(mHeight != NULL){ 18 delete mHeight; //释放栈区数据 19 mHeight = NULL; //防止出现野指针 20 } 21 cout << "析构函数调用" << endl; 22 } 23 int mAge; 24 int *mHeight; 25 }; 26 27 void test01(){ 28 Person p1(18, 160); 29 cout << "P1年龄:" << p1.mAge << "P1身高:" << *p1.mHeight << endl; 30 Person p2(p1); 31 cout << "P2年龄:" << p2.mAge << "P2身高:" << *p2.mHeight << endl; 32 } 33 int main(){ 34 test01(); 35 system("pause"); 36 return 0; 37 }
图解:
因为p1调用了拷贝析构函数,把值赋给了p2,此时是进行了一次浅拷贝,p2的int* m_Heigt没有栈区,它只是把p1的int* m_Heigt保存的地址拷贝了一份给p2的int* m_Heigt,因此p1和p2的int* m_Heigt是共用一个堆区。
由于p2的int* m_Heigt是后进堆区的,在运行完p2后会先背释放,在执行了p2的析构函数后,会把堆区0x0011直接释放掉,再执行p1的析构函数,此时p1也想释放掉堆区0x0011,导致出现了堆区内存重复释放,出现bug。
解决(p2重新申请一块堆区来存放值[与p1地址不同,但是值相同]):
1 #include <iostream> 2 3 using namespace std; 4 5 class Person{ 6 public: 7 Person(){ 8 cout << "默认构造函数调用" << endl; 9 } 10 Person(int age, int height){ 11 mAge = age; 12 mHeight = new int(height); //指针接收堆区数据 13 cout << "有参构造函数调用" << endl; 14 } 15 16 //自己实现拷贝构造函数,解决浅拷贝带来的问题 17 Person(const Person &p){ 18 cout << "拷贝构造函数调用" << endl; 19 mAge = p.mAge; 20 //mHeight = p.mHeight; //编译器默认实现的就是此段代码,也是浅拷贝带来堆区重复释放问题的所在 21 //深拷贝 22 mHeight = new int(*p.mHeight); //申请一个新的内存空间区保存拷贝过来的值 23 } 24 25 ~Person(){ 26 //把堆区开辟的数据释放 27 if(mHeight != NULL){ 28 delete mHeight; //释放栈区数据 29 mHeight = NULL; //防止出现野指针 30 } 31 cout << "析构函数调用" << endl; 32 } 33 int mAge; 34 int *mHeight; 35 }; 36 37 void test01(){ 38 Person p1(18, 160); 39 cout << "P1年龄:" << p1.mAge << "P1身高:" << *p1.mHeight << endl; 40 Person p2(p1); 41 cout << "P2年龄:" << p2.mAge << "P2身高:" << *p2.mHeight << endl; 42 } 43 int main(){ 44 test01(); 45 system("pause"); 46 return 0; 47 }
同时,析构函数也在深拷贝中起到释放堆区内存的作用(就是要自己写析构函数了?悲)
6.初始化列表
作用:对类内的属性进行初始化
语法:
例子:
1 #include <iostream> 2 3 using namespace std; 4 5 class Person{ 6 public: 7 // //传统初始化操作 8 // Person(int a,int b, int c){ 9 // mA = a; 10 // mB = b; 11 // mC = c; 12 // } 13 14 //初始化列表初始化属性 15 Person(int a,int b,int c) : mA(a),mB(b),mC(c){ //可以灵活修改 16 } 17 // Person():mA(10),mB(20),mC(30){ //写死了 18 // } 19 20 int mA; 21 int mB; 22 int mC; 23 }; 24 25 void test01(){ 26 // Person p(10,20,30); //传统赋值操作 27 Person p(30,20,10); //初始化列表初始化属性 28 cout << "mA=" << p.mA << endl; 29 cout << "mB=" << p.mB << endl; 30 cout << "mC=" << p.mC << endl; 31 } 32 33 int main(){ 34 test01(); 35 system("pause"); 36 return 0; 37 }
7.类对象作为类成员(存在隐式转换法)
C++中,类中的成员可以是另一个类的对象,这类成员称为对象成员
例子(其中A a的意思是,以A为类实例化一个对象a):
此时,会先运行类A的构造函数,再去运行类B的构造函数,析构时,会先运行类B的析构函数,再运行类A的
即:当其他类对象作为本类成员时,构造会先构造类对象(对象成员),再构造自身——(先有手机零件,再有手机)
析构会先析构自身,再析构类对象(对象成员)——顺序与构造相反
8.静态成员(static)
再成员变量和成员函数前加上关键字static,称为静态成员
● 静态成员变量
○所有对象共享同一份数据(因此可以通过两种方式进行访问)[1.通过对象进行访问;2.通过类名进行访问]
○在编译阶段分配内存(存在全局区内)
○类内声明,类外初始化(必要操作)
● 静态成员函数
○所有对象共享同一个函数(因此可以通过两种方式进行访问)[1.通过对象进行访问;2.通过类名进行访问]
○静态成员函数只能访问静态成员变量
————————————————————————
①静态成员变量:
例子:
1 #include <iostream> 2 3 using namespace std; 4 5 class Person{ 6 public: 7 static int m_A; 8 }; 9 10 int Person::m_A = 100; //符合类内声明,类外初始化 11 12 void test(){ 13 //1.通过对象进行访问 14 // Person p; 15 // cout << p.m_A << endl; 16 17 //2.通过类名进行访问 18 cout << Person::m_A << endl; 19 } 20 21 int main(){ 22 test(); 23 system("pause"); 24 return 0; 25 }
当然,静态成员变量也可以被赋予权限,如果赋予了private权限(私有权限),那么就不能对其进行访问了。。。
②静态成员函数:
1 #include <iostream> 2 3 using namespace std; 4 5 class Person{ 6 public: 7 static void func(){ 8 A = 10; //静态成员函数可以访问静态成员变量 9 //B = 20; //不可以这样操作,因为B不是静态成员变量,静态成员函数不能访问B,编译器会报错,它无法识别它B到底是哪个对象的 10 cout << "static void func调用" << endl; 11 } 12 13 static int A; 14 int B; 15 }; 16 17 int Person::A = 0; 18 19 void test(){ 20 //1.通过对象访问 21 Person p; 22 p.func(); 23 24 //2.通过类名访问 25 Person::func(); 26 } 27 28 int main(){ 29 30 system("pause"); 31 return 0; 32 }
同样,静态成员函数也可以被赋予权限,如果赋予了private权限(私有权限),那么就不能对其进行访问了。。。
9.成员变量和成员函数的分开存储
C++中,类内成员变量和成员函数分开存储,只有非静态成员变量(非静态成员函数也不属于)才属于类的对象。。
①创建一个空的类
调用空的类来实例化一个空的对象
②创建一个含有一个int类型的非静态成员变量(int占四个字节)
实例化
得到size of p = 4
说明和int所占内存(4字节)相对应,因此m_A属于类的对象
③创建一个含有一个int类型的非静态成员变量和一个int类型的静态成员变量(int占四个字节)
实例化
得到size of p = 4
说明只有非静态成员变量m_A属于类的对象,而静态成员变量m_B不属于类的对象。。
④创建一个含有一个int类型的非静态成员变量和一个int类型的静态成员变量,以及一个非静态成员函数和一个静态成员函数(int占四个字节)
实例化
得到size of p = 4
说明只有非静态成员变量m_A属于类的对象,而静态成员变量m_B,非静态成员函数func和静态成员函数func2均不属于类的对象。。
10.this指针
由于成员变量和成员函数是分开存储,每个非静态成员函数只会诞生一份函数实例(即多个同类型对象会共用一块代码)
此时C++会提供this指针来区分不同的对象调用,this指针指向被调用的成员函数所属的对象
而this指针是隐含每一个非静态成员函数内的一种指针,this指针不需要被定义,可以直接使用
用途:
● 当形参和成员变量同名时,可以使用this指针来区分
● 在类的非静态成员函数中返回对象本身,可使用return *this
例子:
I:解决名称冲突:
此时如果这样调用,编译器会认为Person(int age)内的三个age为同一个,所以实例化对象并且初始化为18的这一步没有成功执行,造成了名称冲突
解决:
①成员内变量和形参变量的名字区分开。
②采用this指针。
1 #include <iostream> 2 3 using namespace std; 4 5 //this指针主要用于: 6 //1.解决名称冲突 7 //2.返回对象本身(采用*this) 8 class Person{ 9 public: 10 Person(int age){ 11 //this指针指向 被调用的成员函数 所属的对象 12 //此处是指向,被调用的成员函数Person,所属的对象p1。 13 this->age = age; 14 } 15 16 int age; 17 }; 18 19 void test01(){ 20 Person p1(18); 21 cout << "p1年龄为:" << p1.age << endl; 22 } 23 24 int main(){ 25 test01(); 26 system("pause"); 27 return 0; 28 }
II:返回对象本身:
1 #include <iostream> 2 3 using namespace std; 4 5 //this指针主要用于: 6 //1.解决名称冲突 7 //2.返回对象本身(采用*this) 8 class Person{ 9 public: 10 Person(int age){ 11 this->age = age; 12 } 13 14 void PersonAddAge(Person &p){ 15 this->age += age; 16 } 17 18 //如果Person不加&(成为了值返回[拷贝构造函数]),会导致每次的返回值都是拷贝一份新的数据,创建了一个新的对象 19 //即运行完p2.PersonAddAge(p1),又创建了一个新的对象(假设为p2'),变成了p2'.PersonAddAge(p1) 20 //此时运行int main()时,输出为:20 21 Person& PersonAddAge1(Person &p){ //引用返回 22 this->age += p.age; 23 //this指向p2的指针,而*this指向的就是p2这个对象的本身(就是自己) 24 return *this; 25 } 26 27 int age; 28 }; 29 30 31 void test02(){ 32 Person p1(10); 33 Person p2(10); 34 //p2.PersonAddAge(p1); 35 //这段代码的意思是: 36 //运行完p2.PersonAddAge(p1),我还想再运行多几次,但是直接写p2.PersonAddAge(p1).PersonAddAge(p1) 37 //编译器会报错,因为void PersonAddAge(Person &p)返回的是空,即:空.PersonAddAge(p1) 38 //因此报错 39 //所以采用Person& PersonAddAge1(Person &p),让函数返回它对象的本身(即此处的p2) 40 //即可实现p2.PersonAddAge1(p1)的多次运行 41 p2.PersonAddAge1(p1).PersonAddAge1(p1).PersonAddAge1(p1); //40 42 cout << "P2的年龄:" << p2.age << endl; 43 } 44 45 int main(){ 46 test02(); 47 system("pause"); 48 return 0; 49 }
注意看此处代码的注释,弄懂*this指针返回自身对象的逻辑。。
11.空指针访问成员函数
C++中空指针也是可以调用成员函数的,但是也要注意有没有用到this指针(调用含有成员变量的函数时)。。
如果用到this指针,需要加以判断(例子中的if)保证代码的健壮性。。
例子:
1 #include <iostream> 2 3 using namespace std; 4 5 class Person{ 6 public: 7 void ShowClassName(){ 8 cout << "Person类" << endl; 9 } 10 11 void showPersonAge(){ 12 //这是一段保证程序健壮的代码 13 //防止空指针称为对象访问类中的成员变量而导致的崩溃 14 if (this == NULL){ 15 return; 16 } 17 18 //空指针访问输出成员变量的函数,会报错,原因是程序中的 mAge 都会变成 this->mAge 19 //this又指向了函数的对象,但是函数的对象是个空指针,变成了打印空对象下的mAge的值 20 //从而引发程序崩溃 21 cout << "Age = " << mAge <<endl; 22 } 23 24 int mAge = 0; 25 }; 26 27 void test01(){ 28 Person *p = NULL; 29 p->ShowClassName(); 30 p->showPersonAge(); 31 } 32 33 int main(){ 34 test01(); 35 system("pause"); 36 return 0; 37 }
12.const(只读)修饰成员函数
● 常函数
○成员函数后加const后我们称为这个函数为常函数
○ 常函数内不可以修改成员属性
○ 成员属性声明时加关键字mutable后,在常函数中依然可以修改
● 常对象
○ 声明对象前加const称该对象为常对象
○ 常对象只能调用常函数
例子:
1 #include <iostream> 2 3 using namespace std; 4 5 class Person{ 6 public: 7 //this指针的本质 是指针常量(指向不能被修改,但是指针指向的值可以被修改) 8 //相当于Person * const this; 9 //const Person * const this; //此时指针指向的值都不可以修改 10 void showPerson() const/*此处的const相当于const Person * const this中的const*/{ 11 this->m_B = 100; //由于采用了mutable,m_B变量变得可以在常函数中被修改 12 //在成员函数后加const,修饰的是this指向,让指针指向的值也不能被修改 13 //m_A = 10; //相当于this->m_A = 10;此时值也不能被修改(只读) 14 } 15 16 void showPerson1(){ 17 m_A = 10; //相当于this->m_A = 10; 18 //this = NULL; //this指针不能修改指针的指向 19 } 20 21 int m_A = 0; 22 mutable int m_B = 0; //能在常函数中被修改的变量(加上了关键字mutable) 23 }; 24 25 //常函数的例子: 26 void test01(){ 27 Person p; 28 p.showPerson(); 29 } 30 31 //常对象的例子: 32 void test02(){ 33 const Person p; //在对象前加const,使之变为常对象 34 //p.m_A = 100; //不允许,常函数内的属性不能被修改 35 p.m_B = 10; //允许,因为m_B被mutable修饰了,可以在常函数中被修改,常对象下也能被修改 36 37 p.showPerson(); //允许,showPerson是常函数,常对象可以调用 38 //p.showPerson1(); //不允许,showPerson1是普通函数,常对象只能调用常函数 39 } 40 41 int main(){ 42 test01(); 43 system("pause"); 44 return 0; 45 }
标签:函数,对象,成员,特性,Person,int,构造函数 From: https://www.cnblogs.com/MorningMaple/p/16867257.html