12.访问权限
公共权限:public 类内可以访问,类外可以访问
保护权限:protected 类内可以访问,类外不可以访问
私有权限:private 类内可以访问,类外不可以访问
13.struct和class的区别
struct和class的唯一区别在于默认的访问权限不同
struct的默认访问权限为公共
class的默认访问权限为私有
14.构造函数和析构函数
如果不提供构造函数和析构函数,编译器会提供,但是空实现
都是编译器自动调用
构造函数:类名(){},可以重载,创建对象时,会自动调用构造函数
析构函数:~类名(){},不可以有参数,因此无法重载,程序在对象销毁前会自动调用析构
15.构造函数的调用和分类
(1)分类
按参数分类:无参构造,有参构造
按类型分类:普通构造和拷贝构造
class Person{
public:
Person()
{
cout<<"Person的无参构造函数"<<endl;
}
Person(int age)
{
this->age=age;
cout<<"Person的有参构造函数"<<endl;
}
Person(const Person& p)
{
this->age=p->age;
cout<<"Person的拷贝构造函数"<<endl;
}
~Person()
{
cout<<"Person的析构函数"<<endl;
}
private:
int age;
};
(2)调用
括号法:
Person p;//无参(默认)构造函数调用
//注意不能写成Person p();编译器会认为这是一个函数声明
Person p2(10);//有参构造函数调用
Person p3(p2);//拷贝构造函数调用
显示法:
Person p;//默认构造函数调用
Person p2=Person(10);//有参构造函数调用
Person p3=Person(p2);//拷贝构造函数调用
Person(10);//匿名对象,当前行结束后,系统会立即回收匿名对象
Person(p3);//会发生错误,因为Person(p3)等价于Person p3,
//会导致重定义,所以不要用拷贝构造函数初始化匿名对象
隐式法:
Person p;//默认构造函数调用
Person p2=10;//有参构造函数调用,相当于Person p2=Person(10);
Person p3=p2;//拷贝构造函数调用,相当于Person p3=Person(p2);
16.拷贝构造函数的调用时机
(1)使用一个创建完毕的对象来初始化一个对象
Person p(10);
Person p2(p);//会调用拷贝构造函数
(2)值传递的方式给函数参数传值
void func(Person p){}
void func2(Person &p){}
int main(){
Person p;
func(p);//值传递会调用拷贝构造函数
func2(p);//采用引用不会调用拷贝构造函数,特别是当数据量大的时候可以节省性能
}
(3)值方式返回局部对象
Person func(){...}
Person& func2(){...}
int main(){
Person p=func();//会调用拷贝构造函数
Person& p2=func2();//不会调用拷贝构造函数
}
17.深拷贝与浅拷贝
浅拷贝:简单的赋值拷贝操作
深拷贝:在堆区重新申请空间,进行拷贝操作
浅拷贝带来的问题就是堆区内存重复释放
class Person(){
private:
int age;
int* height;
public:
Person(){}
Person(int age,int height){
this->age=age;
this->height=new int(height);
}
Person(const Person& p){//浅拷贝,简单的值拷贝
this->age=p.age;
this->height=p.height;
}
~Person(){
if(height!=NULL){
delete height;
heigth=NULL;
}
}
};
int main(){
Person p(10,180);
Person p2(p);
cout<<p.age<<" "<<*(p.height)<<endl;
cout<<p2.age<<" "<<*(p2.height)<<endl;
}
上面的代码会出问题,原因是:
Person p(10,180);//调用有参构造
Person p2(p);//调用拷贝构造
//输出数据
//调用析构函数,p的height不为NULL,释放height所指区域
//调用析构函数,(注意p的height为空,但p2的height不为空,
//仍保存着之前height所指向地方的地址),再次执行delete
//但由于之前的内存已被释放,导致堆区内存重复释放
//将拷贝构造函数修改如下
Person(const Person& p){
this->age=p.age;
//做深拷贝,拷贝数据,而不是单纯拷贝地址
this->height=new int(*p.height);
}
总结:如果属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题,此外,重在赋值运算符时也要注意浅拷贝带来的堆区内存重复释放的问题
18.初始化列表
构造函数():属性名1(值1),属性名2(值2)…{}
class Person{
public:
Person():m_age(1),m_name("张三"){}
Person(int age,string name):m_age(1),m_name("李四"){}
int m_age;
string m_name;
};
19.类对象作为类成员
C++类中的成员可以是另一个类的对象,我们称该成员为对象成员
构造的时候先构造其他类对象,再构造本类对象
析构的时候先析构本类对象,再析构其他类对象
#include<iostream>
using namespace std;
class A{
public:
A(){
cout<<"A的构造函数调用"<<endl;
}
~A(){
cout<<"A的析构函数调用"<<endl;
}
};
class B{
public:
B(){
cout<<"B的构造函数调用"<<endl;
}
~B(){
cout<<"B的析构函数调用"<<endl;
}
A a;
};
int main(){
B b;
}
结果为:
A的构造函数调用
B的构造函数调用
B的析构函数调用
A的析构函数调用