一、面向对象的三大特征
- 封装:把属性(成员变量)和操作(成员函数)结合为一个独立的整体。
- 继承:子类继承父类特征和行为,使得子类(实例)具有夫类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。
- 多态:多态是同一个行为具有多个不同表现形式或形态的能力。
二、Class和struct的联系与区别
- 在C语言中,没有class,只有struct。C语言中的struct叫做结构体,不叫类;
- 在C语言中,struct里只可以有结构体变量,对比类中就是成员变量
- 在C++中,struct和class都是可以定义类的,且struct里既可以有成员变量,也可以有成员变量
- 在C++但是两者的唯一区别就是struct中的成员默认是public类型,而class的成员默认是private
1. class Person
2. {
3. //如果不写public:,相当于这里有一个private:
4. //成员变量
5. int m_age;
6.
7. //成员函数
8. void run()
9. {
10. cout << m_age << "----run()" << endl;
11. }
12. };
13. int main(int argc, char **argv)
14. {
15. Person person1;
16. person1.m_age = 20;//这句代码是出错的,因为m_age是私有变量,外部无法访问
17. person1.run();//这句代码也是出错的,因为run是私有成员函数,外部无法访问
18. return 0;
}
三、类创建多个对象时需要注意的地方
1.不同对象调用同一个成员变量问题
- 类创建非指针对象的方式就是“类名 对象名”,对象调用成员变量的方式就是“对象名.成员变量”
- 类在main函数下创建完对象,就会将对象放在栈空间,分配内存。对象里存储的东西就是类中所有的成员变量(注意:成员函数不在对象的内存里)
- 当类创建多个对象时,不同对象内部都会存放着属于自己的成员变量,尽管调用时名字相同,但其实是地址不同的两个变量(编译器已经自动做过处理,本质上名字也可能不同了)
- 可以看到下方程序的print结果,两个成员变量的地址是不同的,尽管名字相同,但由于地址不同,所以内容还可以不同
1. int main(int argc, char **argv)
2. {
3. Person person1;//占用4个字节,因为Person类下只有一个int型的成员变量,而函数不在对象里,所以对象占用的内存里不包含成员函数
4. person1.m_age = 20;
5. cout << "&person1==" << &person1 << endl;//打印对象地址的方法
6. cout << "person1.m_age==" << &person1.m_age << endl;//打印成员变量地址的方法
7.
8. Person person2;
9. person2.m_age = 30;//person2.m_age的这个m_age跟person1.m_age的m_age,虽然名字相同,但是地址不同,内容也不同,因为对象不一样
10. cout << "&person2.m_age==" << &person2.m_age << endl;//打印成员变量地址的方法
11.
12. //-----------print结果--------------
13. //&person1==000000151C1AF8A4
14. //&person1.m_age==000000151C1AF8A4
15. //&person2.m_age==000000151C1AF8C4
16. //----------------------------------
17.
18. getchar();
19. return 0;
}
指针对象,如何创建呢?有两种创建方法:第一种是先创建普通对象,然后创建一个指针对象指向这个普通对象,但此时指针对象的内存被分配在栈空间;第二种就是直接创建指针对象,将其分配在堆空间。
1. int main(int argc, char **argv)
2. {
3. Person person1;
4. //创建方法1:创建指针对象指向普通对象
5. Person *p1 = &person1;//获取对象的地址,存在指针p里,然后指针p就可以通过对象的地址,间接访问对象的成员变量和成员函数
6. p1->m_age =40;
7. p1->run();//40----run()
8. //上面代码中的person1对象、p指针的内存都是在函数的栈空间,创建时自动分配内存,调用完成后自动回收内存
9. //创建方法2:单纯创建指针对象放在堆空间
10. Person *p2 = new Person;//获取对象的地址,存在指针p里,然后指针p就可以通过对象的地址,间接访问对象的成员变量和成员函数
11. p2->m_age =50;
12. p2->run();//50----run()
13. //上面代码中的pp指针的内存都是在堆空间,需要自己手动回收掉
14. getchar();
15. return 0;
}
2.不同对象调用同一个成员函数
不同的对象里的成员变量都是不同的(尽管名字相同,但是地址值不同)。同时成员变量是存储在对象的内存空间的,但成员函数存储在哪了呢?
成员函数也是存放在代码区的,所以没有存放在对象所在的存储空间,而是单独存放。这样的话,不同的对象调用的成员函数就都是一样的了(名字一样、地址也一样)
问题来了:相同的成员函数,是如何区分不同的对象的呢?往往成员函数里会有成员变量的出现,此时成员函数又是如何做到:不同对象调用这个成员函数,读取不同对象对应的成员变量呢?
答案:类中的成员函数里默认存放着一个隐式参数this,这个this叫做指向当前对象的指针,是编译器自动的存放在成员函数里的指针
1. class Person
2. {
3. public:
4. //成员变量
5. int m_age;
6.
7. //成员函数
8. void run(/*this指针*/)//this叫做隐式参数
9. {
10. this = &person1 &person2 .... this存储着函数调用者的地址,this都有点二级指针的感觉了
11. cout << this->m_age << "----run()" << endl;//其实是执行这两句代码,但编译器给我们省略了
12. cout << m_age << "----run()" << endl;
13. }
14. };
15. int main(int argc, char **argv)
16. {
17. Person person1;
18. person1.m_age = 20;
19. person1.run();//20----run()
20. Person person2;
21. person2.m_age = 30;
22. person2.run();//30----run()
23. //但是这个run函数跟person1.run()是一样的,因为成员函数对于不同的对象来说是共用的
24. getchar();
25. return 0;
}
3.this的作用
背景:假如创建两个对象,person1和person2,成员函数run()由于和对象所在的内存不一样,所以可以让两个对象同时调用。但,run函数是怎么知道是person1调用还是person2调用呢???怎么区分两个对象调用时的区别呢?
答案:这时候编译器就会在成员函数的入口参数默认放置一个this指针,用于接收对象的地址值,这样即使传入的不同对象,this也会立马变成不同的地址值,进而访问不同对象的成员变量,达到不同对象直接调用同一个函数,还能访问自己成员变量的目的
四、C++内存布局问题(易忘点)
C++编程中,主要存在四个内存区域:
- 代码区(内存区)是用来放函数
- 全局区(数据段)是用来存放全局变量
- 栈空间(内存区)用来存放局部变量、函数调用调用时自动分配空间与释放等。每调用一次函数,都会给它分配一段连续的栈空间存放函数中的局部变量
- 堆空间(内存区)内存需要主动申请和释放
调用函数时,代码区里的成员函数代码是怎么访问栈空间里的对象的成员局部变量的呢?
将栈空间里的局部变量的地址值传给代码区里的函数,这样函数就可以找到存在栈空间的局部变量了;
怎么将地址值传给函数的呢?
用到了指向当前对象的指针thi,编译器给自动写好了,所以看不到,但本质是这样操作的;
栈空间里的对象的成员变量的地址和这个对象的地址有什么区别?
每一个对象实例都在内存中有一个唯一的地址,表示该对象在内存中的位置。成员变量会被分配在对象的内存空间中,成员变量的地址是相对于对象地址的偏移量,可以通过成员变量的偏移量来计算并访问成员变量的地址,因此成员变量的地址和对象的地址是相关联的,但并不完全相同,而是相对关系。
总结
- 函数就是存放在代码区的,无论是自定义的普通函数、类中的成员函数,以及main函数,都是存放在代码区的(跟栈空间不一样,栈空间与函数的关系,就是调用函数时,开辟一段连续的栈空间存放函数内的局部变量)
- 哪个对象调用成员函数,this指针就执行哪个对象
- 在C++中,struct和class都是可以定义类的,但是两者的唯一区别就是struct中的成员,默认是public类型,而class的成员默认是private类型
补充
变量在命名时,尽量有一个标准,比如采用下方习惯,会使代码可读性更高