类与对象 —— 对象特性
作者:互联网
对象的初始化和清理
- 生活中我们买的电子产品都基本会有出厂设置,在某一天我们不用时候也会删除一些自己信息数据保证安全
- C++中的面向对象来源于生活,每个对象也都会有初始设置以及 对象销毁前的清理数据的设置。
4.2.1 构造函数和析构函数
对象的初始化和清理也是两个非常重要的安全问题
一个对象或者变量没有初始状态,对其使用后果是未知
同样的使用完一个对象或变量,没有及时清理,也会造成一定的安全问题
c++利用了构造函数和析构函数解决上述问题,这两个函数将会被编译器自动调用,完成对象初始化和清理工作。
对象的初始化和清理工作是编译器强制要我们做的事情,因此如果我们不提供构造和析构,编译器会提供
编译器提供的构造函数和析构函数是空实现。
- 构造函数:主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用。
- 析构函数:主要作用在于对象销毁前系统自动调用,执行一些清理工作。
构造函数语法:类名(){}
- 构造函数,没有返回值也不写void
- 函数名称与类名相同
- 构造函数可以有参数,因此可以发生重载
- 程序在调用对象时候会自动调用构造,无须手动调用,而且只会调用一次
析构函数语法: ~类名(){}
- 析构函数,没有返回值也不写void
- 函数名称与类名相同,在名称前加上符号 ~
- 析构函数不可以有参数,因此不可以发生重载
- 程序在对象销毁前会自动调用析构,无须手动调用,而且只会调用一次
1 #include <iostream> 2 using namespace std; 3 class person 4 { 5 public: 6 person() 7 { 8 cout << "person的构造函数" << endl; 9 } 10 ~person() 11 { 12 cout << "person的析构函数函数" << endl; 13 } 14 }; 15 //构造与析构都必须有实现,如果我们不提供,编译器会自动提供一个空实现的构造与析构· 16 void test01() 17 { 18 person p;//栈区函数,函数结束后会自动重置 19 } 20 21 int main() 22 { 23 //test01(); 24 person p; 25 system("pause");//再按任意键之前main函数不会结束所以析构函数不会出现 26 return 0; 27 }
1 #include <iostream> 2 using namespace std; 3 class person 4 { 5 public: 6 person() 7 { 8 cout << "person的构造函数" << endl; 9 } 10 ~person() 11 { 12 cout << "person的析构函数函数" << endl; 13 } 14 }; 15 //构造与析构都必须有实现,如果我们不提供,编译器会自动提供一个空实现的构造与析构· 16 void test01() 17 { 18 person p;//栈区函数,函数结束后会自动重置 19 } 20 21 int main() 22 { 23 test01();//执行完后析构函数将自动出现 24 //person p; 25 system("pause"); 26 return 0; 27 }
构造函数分类与三种输出方法:
分类与括号法:
1 #include <iostream> 2 using namespace std; 3 class person 4 { 5 public: 6 //构造函数按参数分为: 有参构造和无参构造(默认构造) 7 //普通构造函数 8 person() 9 { 10 cout << "person 无参构造函数" << endl; 11 } 12 person(int a) 13 { 14 age = a; 15 cout << "person 有参构造函数" << endl; 16 } 17 //构造函数按类型分为: 普通构造和拷贝构造 18 //拷贝构造 19 person(const person& p)//const保证不修改原函数,用引用的方式是因为我们不是调用原函数 20 { 21 //将传入的人的所有的属性拷贝到当前对象上 22 age = p.age; 23 cout << "person 拷贝构造函数" << endl; 24 }; 25 int age; 26 }; 27 void test01() 28 { 29 //1.括号法 30 person p1;//无参构造函数 31 /* 注意事项1 32 调用默认构造函数时不要加() 33 因为编译器会认为是一个函数声明 34 person p1(); 35 void fuc() 这就是函数声明;*/ 36 person p2(10);//有参构造函数 37 person p3(p1);//拷贝构造函数 里面是p1 p2均可 38 cout << "p2年龄为" << p2.age << endl; 39 cout << "p3年龄为" << p3.age << endl; 40 } 41 int main() 42 { 43 test01(); 44 }
显示法:
1 2 // 显示法 3 person p1; 4 person p2 = person(10);//与括号法调用方式 :person p2(10);对比 5 person p3 = person(p2);//与括号法调用方式 :person p3(p2);对比
匿名对象:
1 #include <iostream> 2 using namespace std; 3 class person 4 { 5 public: 6 //构造函数按参数分为: 有参构造和无参构造(默认构造) 7 //普通构造函数 8 person() 9 { 10 cout << "person 无参构造函数" << endl; 11 } 12 person(int a) 13 { 14 age = a; 15 cout << "person 有参构造函数" << endl; 16 } 17 //构造函数按类型分为: 普通构造和拷贝构造 18 //拷贝构造 19 person(const person& p)//const保证不修改原函数,用引用的方式是因为我们不是调用原函数 20 { 21 //将传入的人的所有的属性拷贝到当前对象上 22 age = p.age; 23 cout << "person 拷贝构造函数" << endl; 24 }; 25 ~person() 26 { 27 cout << "person 析构函数调用" << endl; 28 } 29 int age; 30 }; 31 void test01() 32 { 33 person(10);//匿名对象 特点:当前执行结束后,系统会立即回收掉匿名对象 34 cout << "aaa"; 35 } 36 int main() 37 { 38 test01(); 39 }
注意事项二 :不要用拷贝构造函数初始化匿名对象
隐式转换法
1 person p4 = 25;//相当于写了 person p4 = person(10);有参构造函数
2 person p5 = p4;//拷贝构造函数
拷贝构造函数调用时机
C++中拷贝构造函数调用时机通常有三种情况
- 使用一个已经创建完毕的对象来初始化一个新对象
- 值传递的方式给函数参数传值
- 以值方式返回局部对象
1 #include <iostream> 2 using namespace std; 3 class person 4 { 5 public: 6 person() 7 { 8 cout << "person 的无参构造函数的调用" << endl; 9 } 10 person(int age) 11 { 12 m_age = age; 13 cout << "person 的有参构造函数的调用" << endl; 14 cout << "参构造函数中的m_age:" << m_age << endl; 15 } 16 ~person() 17 { 18 cout << "person 的析构函数的调用" << endl; 19 } 20 person(const person& p)//传入类的别名并且限制修改类中的元素和修饰类的地址 21 { 22 cout << "person 的拷贝构造函数的调用" << endl; 23 //m_age = p.m_age;//将 24 //cout << "拷贝构造函数中的" << m_age << endl; 25 cout << "拷贝构造函数中的 p.m_age:" << p.m_age << endl; 26 } 27 int m_age; 28 }; 29 //使用一个已经创建完毕的对象来初始化一个新对象 30 void test01() 31 { 32 person p1(20); 33 person p2(p1); 34 } 35 //2. 值传递(给一个函数的参数进行传值)的方式给函数参数传值 36 //相当于Person p1 = p; 37 void dowork(person p) 38 { 39 40 } 41 void test02() 42 { 43 person p; 44 dowork(p);//值传递的本质是给原函数拷贝一个副本出来输出,这符合拷贝构造函数的调用条件 45 } 46 //3. 以值方式返回局部对象 47 person dowork2() 48 { 49 person p1;//再执行完之后会释放掉,所以会触发析构函数调用 50 cout << (int*)&p1 << endl; 51 return p1;//不会返回person p1中的p1,而是拷贝一份返回出去,这符合拷贝构造函数调用条件 52 } 53 void test03() 54 { 55 person p = dowork2(); 56 cout << (int*)&p << endl; 57 } 58 int main() 59 { 60 //test01(); 61 //test02(); 62 test03(); 63 } 64 65
构造函数调用规则
默认情况下,c++编译器至少给一个类添加3个函数
1.默认构造函数(无参,函数体为空)
2.默认析构函数(无参,函数体为空)
3.默认拷贝构造函数,对属性进行值拷贝
构造函数调用规则如下:
-
如果用户定义有参构造函数,c++不在提供默认无参构造,但是会提供默认拷贝构造
-
如果用户定义拷贝构造函数,c++不会再提供其他构造函数
1 class Person { 2 public: 3 //无参(默认)构造函数 4 Person() { 5 cout << "无参构造函数!" << endl; 6 } 7 //有参构造函数 8 Person(int a) { 9 age = a; 10 cout << "有参构造函数!" << endl; 11 } 12 //拷贝构造函数 13 Person(const Person& p) { 14 age = p.age; 15 cout << "拷贝构造函数!" << endl; 16 } 17 //析构函数 18 ~Person() { 19 cout << "析构函数!" << endl; 20 } 21 public: 22 int age; 23 }; 24 25 void test01() 26 { 27 Person p1(18); 28 //如果不写拷贝构造,编译器会自动添加拷贝构造,并且做浅拷贝操作 29 Person p2(p1); 30 31 cout << "p2的年龄为: " << p2.age << endl; 32 } 33 34 void test02() 35 { 36 //如果用户提供有参构造,编译器不会提供默认构造,会提供拷贝构造 37 Person p1; //此时如果用户自己没有提供默认构造,会出错 38 Person p2(10); //用户提供的有参 39 Person p3(p2); //此时如果用户没有提供拷贝构造,编译器会提供 40 41 //如果用户提供拷贝构造,编译器不会提供其他构造函数 42 Person p4; //此时如果用户自己没有提供默认构造,会出错 43 Person p5(10); //此时如果用户自己没有提供有参,会出错 44 Person p6(p5); //用户自己提供拷贝构造 45 } 46 47 int main() { 48 49 test01(); 50 51 system("pause"); 52 53 return 0; 54 }
深拷贝与浅拷贝
深浅拷贝是面试经典问题,也是常见的一个坑
浅拷贝:简单的赋值拷贝操作
深拷贝:在堆区重新申请空间,进行拷贝操作
1 #include <iostream> 2 using namespace std; 3 class person 4 { 5 public: 6 person() 7 { 8 cout << "person的默认构造函数调用" << endl; 9 } 10 person(int age ,int height) 11 { 12 m_age = age; 13 m_height = new int(height); 14 cout << "person的有参构造函数调用" << endl; 15 } 16 int m_age; 17 int* m_height; 18 ~person()//析构代码的用途 : 释放构造函数在堆区的内存 19 { 20 if (m_height != NULL) 21 { 22 delete m_height; 23 m_height = NULL; 24 } 25 cout << "person的析构函数调用" << endl; 26 } 27 person(const person& p) 28 { 29 cout << "person拷贝构造函数调用的调用" << endl; 30 //系统会自动执行实现m_ age = p.m_age;m_height =p.m_height; 31 //但因为拷贝函数和原函数的height指向同一个地址开辟在堆区,释放时会有冲突,所以我们需要 32 //深拷贝操作 33 m_height = new int(*p.m_height); 34 } 35 }; 36 void test01() 37 { 38 person p1(18,160); 39 cout << "p1 age is :" << p1.m_age << ",and his height is : " << *p1.m_height<< endl; 40 person p2(p1); 41 cout << "p2 age is :" << p2.m_age << ",and his height is : " <<* p2.m_height << endl; 42 } 43 int main() 44 { 45 test01(); 46 }
初始化列表
作用:
C++提供了初始化列表语法,用来初始化属性
语法:构造函数():属性1(值1),属性2(值2)... {}
初始化数据成员与对数据成员赋值的含义是什么?有什么区别?
首先把数据成员按类型分类并分情况说明:
- 1.内置数据类型,复合类型(指针,引用)- 在成员初始化列表和构造函数体内进行,在性能和结果上都是一样的
- 2.用户定义类型(类类型)- 结果上相同,但是性能上存在很大的差别。因为类类型的数据成员对象在进入函数体前已经构造完成,也就是说在成员初始化列表处进行构造对象的工作,调用构造函数,在进入函数体之后,进行的是对已经构造好的类对象的赋值,又调用个拷贝赋值操作符才能完成(如果并未提供,则使用编译器提供的默认按成员赋值行为)
注意点:
初始化列表的成员初始化顺序:
C++ 初始化类成员时,是按照声明的顺序初始化的,而不是按照出现在初始化列表中的顺序。
1 class CMyClass { 2 CMyClass(int x, int y); 3 int m_x; 4 int m_y; 5 }; 6 7 CMyClass::CMyClass(int x, int y) : m_y(y), m_x(m_y) 8 { 9 };
你可能以为上面的代码将会首先做 m_y=I,然后做 m_x=m_y,最后它们有相同的值。但是编译器先初始化 m_x,然后是 m_y,,因为它们是按这样的顺序声明的。结果是 m_x 将有一个不可预测的值。有两种方法避免它,一个是总是按照你希望它们被初始化的顺序声明成员,第二个是,如果你决定使用初始化列表,总是按照它们声明的顺序罗列这些成员。这将有助于消除混淆。
1 #include <iostream> 2 using namespace std; 3 class person 4 { 5 public: 6 person(int a,int b ,int c) :m_a(a), m_b(b), m_c(c) 7 { 8 9 } 10 int m_a; 11 int m_b; 12 int m_c; 13 }; 14 void test01() 15 { 16 person p(30,20,10); 17 cout << "m_a = " << p.m_a << endl 18 << "m_b = " << p.m_b << endl 19 << "m_c = " << p.m_c << endl; 20 21 } 22 int main() 23 { 24 test01(); 25 }
类对象作为类成员
C++类中的成员可以是另一个类的对象,我们称该成员为 对象成员
例如:
1 class A {}
2 class B
3 { A a; }
B类中有对象A作为成员,A为对象成员
那么当创建B对象时,A与B的构造和析构的顺序是谁先谁后?
1 #include <iostream> 2 #include <string> 3 using namespace std; 4 class phone 5 { 6 public: 7 string m_pname;//创建一个名为m_pname 的string型变量 8 phone(string pname) 9 { 10 m_pname = pname; 11 cout << "phone的构造函数调用" << endl; 12 } 13 ~phone() 14 { 15 cout << "phone的析构函数调用" << endl; 16 } 17 }; 18 class person 19 { 20 public: 21 person(string name,string pname):m_name(name), m_phname(pname) 22 { 23 cout << "person的构造函数调用" << endl; 24 } 25 ~person() 26 { 27 cout << "person的析构函数调用" << endl; 28 } 29 phone m_phname;//m_phname 为phone类的变量 30 string m_name; 31 }; 32 void test01() 33 { 34 //当类中成员是其他类对象时,我们称该成员为 对象成员 35 //构造的顺序是 :先调用对象成员的构造,再调用本类构造 36 //析构顺序与构造相反,因为栈区有先进后出的原则 37 person p("张三","iphone"); 38 cout << "名字 :" << p.m_name << ",手机 :" << p.m_phname.m_pname << endl; 39 } 40 int main() 41 { 42 test01(); 43 }
静态成员
静态成员就是在成员变量和成员函数前加上关键字static,称为静态成员
静态成员分为:
- 静态成员变量
- 所有对象共享同一份数据
- 在编译阶段分配内存
- 类内声明,类外初始化
-
1 #include <iostream> 2 using namespace std; 3 class person 4 { 5 public: 6 //所有对象都共享同一份数据 7 // 编译阶段就分配了内存 8 //类内声明,类外初始化操作 9 static int m_a;//类内声明,编译阶段分配了内存 10 //静态成员变量也是有访问权限的 11 private: 12 static int m_b; 13 }; 14 int person::m_a = 100;//类外初始化 15 int person::m_b = 200; 16 void test01() 17 { 18 person p; 19 cout <<"修改前p.m_a :" << p.m_a << endl; 20 person p2; 21 p2.m_a = 200; 22 cout << "利用p2修改后p.m_a :" << p.m_a << endl;//所有对象都共享同一份数据 ,当一个对象对数据进行修改,其他的对象共享的数据也变成了修改后的数据 23 cout << "p2.m_a :" << p2.m_a << endl; 24 } 25 void test02() 26 { 27 //静态成员变量 不属于某一个对象上 所有对象都共享同一份数据 28 //因此静态成员变量有两种的访问方式 29 30 //第一种:通过对象进行访问 31 /* person p; 32 cout << p.m_a << endl;*/ 33 //第二种:通过类目进行访问 34 cout << person::m_a << endl; 35 // cout << person::m_b << endl; 类外访问不到私有的静态成员变量 36 } 37 int main() 38 { 39 //test01(); 40 test02(); 41 }
- 静态成员函数
- 所有对象共享同一个函数
- 静态成员函数只能访问静态成员变量
1 #include <iostream> 2 using namespace std; 3 //静态成员函数 4 //所有对象共享同一个函数 5 //静态成员函数只能访问静态成员变量 6 class person 7 { 8 public: 9 //静态成员函数 10 static void func() 11 { 12 m_a = 100;//静态成员函数可以访问静态成员变量,所有对象共享的 13 m_b = 200;//静态成员函数不可以访问非静态成员变量,无法区分到底是哪个对象的m_b属性;运行时会报错 14 cout << "static void func()的调用" << endl; 15 } 16 static int m_a;//静态成员变量 17 private: 18 static void func2() 19 { 20 cout << "static void func2()的调用" << endl; 21 } 22 }; 23 int person::m_a = 0;//静态成员变量 24 int m_b = 1;//非静态成员变量 25 //有两种访问方式 26 void test01() 27 { 28 //通过对象进行访问 29 person p; 30 p.func(); 31 //通过类名进行访问 32 person::func(); 33 //person::func2();//错误操作,私有作用域下类外不可访问 34 } 35 int main() 36 { 37 test01(); 38 }
- 静态成员函数
标签:初始化,对象,成员,特性,person,int,构造函数 来源: https://www.cnblogs.com/zaiyewujiang/p/16629571.html