首页 > 其他分享 >类和对象——对象特性

类和对象——对象特性

时间:2022-11-09 19:33:30浏览次数:27  
标签:函数 对象 成员 特性 Person int 构造函数

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

相关文章