文章目录
面向对象
🥑c语言是面向过程的,关注的是过程
🥑c++是基于面向对象的,关注的是对象
🎨面向对象三大特性:封装,继承,多态
封装:🎈1.数据和方法在类里面都放在了一起
🎈 2.访问限定符:public(公有) protected(保护) private(私有)
🔰公有可以在类外面直接访问;保护和私有不可以直接访问
🎈封装是一种更好的严格管理,不封装是一种自由管理
类和对象
1.struct
在c语言中学会了如何创建和使用一个结构体
struct student
{
//成员变量
char _name[20];
int _age;
};
但是c++中,升级到类,student是类名,也是类型
struct 的默认访问限定符是公有public
//c++类跟结构体不同的是除了可以定义变量,还可以定义方法和函数
struct student
{
//成员变量
char _name[20];
int _age;
//成员方法
void Init(const char* name, int age)
{
strcpy(_name, name);
_age = age;
}
void Print()
{
cout << _name << endl;
cout << _age << endl;
}
};
int main()
{
struct student s1;//兼容c,这里struct student才是类型
student s2; //升级到类,student是类名,也是类型
s1.Init("张三", 13);
s1.Print();
return 0;
}
2.class
把struct换成class,暂时class类就定义出来了
🦇class的默认访问限定符是私有
🦇因此我们可以加访问限定符,限定范围的是该限定符的位置到下一个限定符,如果后面没有限定符,默认到类的结束
class student
{
private:
//成员变量
char _name[20];
int _age;
public://限定范围的是该限定符的位置到下一个限定符
//成员方法
void Init(const char* name, int age)
{
strcpy(_name, name);
_age = age;
}
//private:
void Print()
{
cout << _name << endl;
cout << _age << endl;
}
};
类对象的大小
🐞对象中存了成员变量,没存成员函数
因为每个对象都有独立的成员变量
不同对象调用成员函数,调的是同一个
🐞只保存成员变量,成员函数放在公共的代码段
结论:
计算类或者类对象大小,只看成员变量,考虑内存对齐,c++内存对齐规则跟c语言结构体一致
空类会给一个byte,这1byte不存储有效数据,只是为了和其他类区分
this指针
首先创建一个日期类
class Date
{
private:
int _year;
int _month;
int _day;
public:
void Init(int year,int month,int day)
{
_year = year;
//即使成员变量跟形参同名,根据就近原则,还是会优先使用形参,但结果不是我们传的2022
//想同名,也可以使用Date :: year,指定作用域
_month = month;
_day = day;
}
void Print()
{
cout << _year<<"-" << _month << "-" << _day << endl;
}
};
int main()
{
Date s1;
s1.Init(2022, 12, 10);
s1.Print();
Date s2;
s2.Init(1998, 2, 12);
s2.Print();
return 0;
}
🔰注意:在c++中,s2.Print();
表面上没有传任何参数,实际上它隐藏一个参数this 指针
在这个类里,this指针的类型是Date*
💙 “//”意思是都是被编译器处理过后的
//void Init(Date* this,int year,int month,int day)
//void Print(Data* this)
void Print()
{
cout << _year<<"-" << _month << "-" << _day << endl;
//只有在成员变量之前加this->
//cout << this->_year << "-" <<this-> _month << "-" <<this-> _day << endl;
}
d1.Print();//会被编译器处理成d1.Print(&d1)
1. 调用成员函数时,不能显示传实参给this; 2.定义成员函数时,不能显示声明形参this;3.但是在成员函数内部 ,我们可以显示使用this |
所以这么编写是错的void Print(Data* this)
和d1.Print(&d1)
而下面这段在成员函数内部使用就是对的
void Init(int year,int month,int day)
{
this->_year = year;
this->_month = month;
this->_day = day;
}
this指针本身不能被改变(Date* const this)
const 在Date*之前表示指向的内容不能被改变 在Date* 之后表示指针本身不能被改变
this存在哪?一般情况下是在栈(形参),有些编译器会放在寄存器中。
1.重要例题
// 1.下面程序能编译通过吗?
// 2.下面程序会崩溃吗?在哪里崩溃
class A
{
public:
void Show()//虽然传过来的是空指针,但并没有进行解引用操作
{
cout<<"Show()"<<endl;//这里没有错误,正常运行
}
void PrintA()
{
cout<<_a<<endl;//这里会运行崩溃
//这里编译器会这么处理 cout<<this->_a<<endl;
//this又是空指针,解引用了就会报错
}
private:
int _a;
};
int main()
{
A* p = nullptr;
p->Show();//这里没有错误,正常运行
p->PrintA();//这里就会运行崩溃
}
分析:
1.p虽然是空指针,但是p调用成员函数不会编译报错,因为空指针不是语法错误,编译器检查不出来
2.p虽然是空指针,但是p调用成员函数不会出现空指针访问。因为成员函数没有存在对象里
3.这里会把p作为实参传递给隐藏的this指针,而指针不解引用是不会报错的
类的默认成员函数
1.构造函数
构造函数是特殊的成员函数,需要注意的是构造函数的主要任务并不是开空间创建对象,而是初始化对象。
其特征如下:
- 函数名与类名相同。
- 无返回值。
- 对象实例化时编译器自动调用对应的构造函数。
- 构造函数可以重载
public:
//Date()//无参
//构成函数的重载,但是它要是无参的调用,就会有二义性
//{
// _year=0;
// _month=1;
// _day=1;
//}
Date(int year = 2000, int month = 1, int day = 10)//全缺省
{
_year = year;
_month = month;
_day = day;
}
- 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成,但是它没有做初始化的工作
c++里把类型分为两类:内置类型,自定义类型
内置类型:int / char / double /指针 /数组 等
自定义类型:struct / class 定义的类型
🛎编译器默认的构造函数,对于内置类型不做初始化处理
🛎对于自定义类型会去调用他的无参默认构造函数(不用参数就可以调用)初始化,如果没有默认构造函数就会报错
任何一个类的默认构造函数就是–不用参数就可以调用(1.全缺省;2.无参;3.不写编译器默认生成的)
无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个 |
//定义一个对象必须是下面两种形式
Date d1;//无参,也可以进入全缺省的构造函数
Date d2(2000,10,22);//当构造函数是我们自己写的缺省参数
//不可以Date d1();
2.析构函数
对象在销毁时会自动调用析构函数,完成对象的一些资源清理工作
像日期类,没有资源需要清理,所以Date不实现析构函数是可以的
像栈,队列等等动态开辟的则需要我们自己去写析构函数
如果我们不写,默认生成的析构函数和构造函数类似
对内置类型的成员变量不做处理,对自定义类型会去调用他的析构函数
析构函数是特殊的成员函数。
其特征如下:
- 析构函数名是在类名前加上字符 ~。
- 无参数无返回值。
- 一个类有且只有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。
- 对象生命周期结束时,C++编译系统系统自动调用析构函数。
class Stack
{
public:
Stack(int capacity=4)
{
_a = (int*)malloc(sizeof(int) * capacity);
if (_a == nullptr)
{
cout << "malloc fail" << endl;
exit(-1);
}
_top = 0;
_capacity = capacity;
}
~Stack()
{
free(_a);
_a = nullptr;
_top = _capacity = 0;
}
private:
int _capacity;
int _top;
int* _a;
};
int main()
{
Stack s1;
return 0;
}
总结:析构函数完成对象中资源的清理,如果类对象需要资源清理才要自己实现析构函数
析构函数对象周期到了,以后自动调用,正确实现了析构函数,保证了类对象中的资源被清理
3.拷贝构造函数
函数名和类名相同,没有返回值,是构造函数的一个重载
- 拷贝构造函数是构造函数的一个重载形式。
- 拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用。
传值传参形参是实参的一份拷贝,这个拷贝就会调用拷贝构造,就会套娃,而Date& 是别名
c语言就没有拷贝构造
c++里所有的传值传参都是拷贝构造
Date(const Date& d)//因为参数不用被修改,也能防止d2改变d1
{
_year = d._year;
_month = d._month;
_day = d._day;
}
如果我们没有定义拷贝构造函数,系统会生成默认的拷贝构造函数
1.内置类型成员,会完成按字节序的值 拷贝(浅拷贝)
比如:d1照样拷贝给d2
Date d1(2000, 10, 11);
Date d2(d1);
对于栈这种类就不能用默认的拷贝构造,需要自己去完成
2.自定义类型成员,会调用他的拷贝构造
拷贝构造我们不写,编译器生成的默认拷贝构造函数,对于内置类型和自定义类型都会拷贝处理,但是处理的细节不一样,这是跟构造和析构不一样的
例题
构造的顺序:C A B D
C是全局变量,C先初始化,AB是局部变量,D是静态局部变量,但A先定义,B再定义,D最后定义
析构的顺序:B A C D
在相同的生命周期下,先定义的后析构,生命周期短的先析构,由于A和B生命周期相同,C全局变量和D静态局部变量生命周期相同,A比B先定义,C比D先定义
所以调用析构函数的顺序是B A C D
class Widget
{
public:
Widget()
{
//cout << "D()" << endl;
}
//拷贝构造,会优化
Widget(const Widget& d)
{
cout << "Widget(const Widget& d)" << endl;
}
//只要不是构造就不会优化
Widget& operator=(const Widget& d)
{
cout << "Widget& operator=(const Widget&)" << endl;
return *this;
}
};
Widget f(Widget u)//传值返回又会调用一次拷贝构造
{
return u;
}
int main()
{
//Widget x;//生命周期在当前函数
//f(x);//传值传参会调用一次拷贝构造
//Widget y = f(x);
//拿一个值接收它,又会调用一次拷贝构造
//一次调用里面,连续的构造函数,会被编译器优化,合二为一
//优化之后就只会调用两次拷贝构造
//Widget x;
//Widget y;
//赋值就不会优化
//y = f(x);
//匿名对象的生命周期只在这一行
Widget();
return 0;
}
4.赋值运算符重载
默认情况下c++是不支持自定义类型对象使用运算符
//函数名 operator操作符
//返回类型,看操作符运算后的返回值是什么
bool operator>(const Date& d1, const Date& d2)
//const Date& 的好处:既可以接收const Date&类型的对象,也可以接收非const类型的对象
//因为Date&类型的对象传给const Date& ,是权限的缩小
我们可以把它放在类里面,这样_year,_month,_day都能被访问了
👼因为在类当中,成员函数有隐藏一个参数this
👼所以我们要改成bool operator>( const Date& d2) //会被编译器处理成bool operator>(Date* const this,const Date& d)
所以最终在类里就是如下代码
class Date
{
public:
//构造函数
Date(int year = 1000, int month = 1, int day = 10)
{
_year = year;
_month = month;
_day = day;
}
bool operator>(const Date& d)//会被编译器处理成bool operator>(Date* const this,const Date& d)
{
if (_year > d._year)
{
return true;
}
else if (_year == d._year && _month > d._month)
{
return true;
}
else if (_year == d._year && _month == d._month && _day > d._day)
{
return true;
}
else
{
return false;
}
}
private:
int _year;
int _month;
int _day;
};
const 成员
Date d1;
d1.Print();
//为什么d1能调用,d2不行呢?
//因为权限放大了
const Date d2;
d2.Print();
所以c++增加了const 成员函数
//声明
void Print()const;
//定义
void Date::Print()const
双const表示,this本身不能被改,this指向的内容也不能被改
成员函数加const是好的,能加就加,这样普通对象和const对象都可以调用
但是如果要修改成员变量的成员函数是不能加的
比如日期类的++ – -=…
取地址运算符重载
A* operator&()//默认的构造函数,不需要自己写
{
return this;
}
const A* operator&()const
{
return this;
}