类和对象
预处理、编译、汇编、链接过程
- 预处理:头文件展开、宏替换、条件编译、去除注释 ->
.i
- 编译:检查语法、生成汇编代码 ->
.s
- 汇编:将汇编指令转换成CPU能理解的二进制机器码 ->
.o
- 链接:找函数实体、生成可执行文件 -> Windows:
.exe
, Linux:.out
为什么C++ 支持重载,C语言不支持?
- C语言:在链接之后生成的目标文件中函数名的存储方式是直接使用函数名,因此出现命名冲突。
- C++:使用函数名修饰规则,Linux下函数名的存储格式为:
_Z+函数名长度+函数名+参数类型首字母
,因此只要符合重载规则的函数名都可重复调用。
函数缺省参数
全缺省:使用时要从左往右依次传值。
void Func(int a = 10, int b = 20);
半缺省:声明时要从左往右缺省,而且连续。
void Func2(int a, int b=20, int c = 40);
拷贝构造
文章链接
C++自动提供了以下默认成员函数(在没有显式定义的情况下):
- 默认构造函;
- 默认析构函数;
- 拷贝构造函数(浅拷贝);
- 赋值运算符(operator=);
- 地址运算符;
当出现类的等号赋值时,会调用拷贝函数,在未定义显示拷贝构造函数的情况下,系统会调用默认的拷贝函数——即浅拷贝,
它能够完成成员的复制
。当数据成员中没有指针时,浅拷贝是可行的。但当数据成员中有指针时,如果采用简单的浅拷贝,则两类中的两个指针将指向同一个地址,当对象快结束时,会调用两次析构函数,而导致指针悬挂现象。所以,这时,必须采用深拷贝。
深拷贝与浅拷贝的区别就在于深拷贝会在堆内存中另外申请空间来储存数据
,从而也就解决了指针悬挂的问题。简而言之,当数据成员中有指针时,必须要用深拷贝。
浅拷贝 vs 深拷贝
- 浅拷贝:仅复制对象中的值,不复制指针所指向的资源。如果对象包含指针,两个对象将共享同一个指针,导致析构时的指针悬挂问题。
class Rect {
public:
Rect() { p = new int(100); }
~Rect() { delete p; }
private:
int *p;
};
- 深拷贝:不仅复制对象中的值,还在堆内存中重新分配空间并复制指针所指向的资源。
class Rect {
public:
Rect() { p = new int(100); }
Rect(const Rect& r) {
p = new int(*r.p);
}
~Rect() { delete p; }
private:
int *p;
};
Date方法类中运算符重载:
class Date {
public:
int GetMonthDay(int year, int month) const{
static int months[13] = { 0, 31, 28, 31, 30 , 31, 30 , 31, 31 , 30, 31 , 30, 31};
// 闰年2月返回29天
if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0)){
return 29;
}
return months[month];
}
// 运算符重载(内部实现)
bool operator==(const Date& d) const { // => bool operator==(Data* this, const Date& d)
return _year == d._year // this->_year == d._year
&& _month == d._month
&& _day == d._day;
}
bool operator>(const Date& d) const {
return !(*this <= d); // 复用已实现的<=运算符
}
bool operator<(const Date& d) const {
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;
return false;
}
// d1 <= d2 -> d1.operator<=(&d1, d2)
bool operator<=(const Date& d) const { // bool operator<=(Date *this, const Date& d)
return *this < d || *this == d; // 复用已实现的方法
}
bool operator>=(const Date& d) const {
return !(*this < d);
}
bool operator!=(const Date& d) const {
return !(*this == d);
}
Date operator+(int day) const {
Date res(*this);
res._day += day;
while (res._day > GetMonthDay(res._year, res._month)) {
res._day -= GetMonthDay(res._year, res._month);
res._month++;
if (res._month == 13) {
res._year++;
res._month = 1;
}
}
return res;
}
// Date& 加&可以避免(*this)不必要的拷贝构造
// 判断是否要用引用返回值:出了函数对象还在就可用引用返回
Date& operator+=(int day) {
if (day < 0) {
return *this -= -day;
}
_day += day;
while (_day > GetMonthDay(_year, _month)) {
_day -= GetMonthDay(_year, _month);
_month++;
if (_month == 13) {
_year++;
_month = 1;
}
}
return *this;
}
Date operator-(int day) const {
Date res(*this);
res._day -= day;
while (res._day <= 0) {
res._month--;
if (res._month == 0) {
res._year--;
res._month = 12;
}
res._day += GetMonthDay(res._year, res._month);
}
return res;
}
Date& operator-=(int day) {
if (day < 0) {
return *this += -day;
}
_day -= day;
while (_day <= 0) {
_month--;
if (_month == 0) {
_year--;
_month = 12;
}
_day += GetMonthDay(_year, _month);
}
return *this;
}
// d2 = d1 -> d3.operator=(d1)
Date& operator=(const Date& d) {
if (this != &d) { // 避免自我赋值
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this; // 返回对象本身
}
// ++d1
Date& operator++() {
*this += 1;
return *this;
}
// d1++ -> d1.operator++(&d1, 0)
Date operator++(int) {
Date tmp(*this);
*this += 1;
return tmp;
}
// d1 - d2
int operator-(const Date& d) const {
int flag = 1;
Date max = *this;
Date min = d;
if (*this < d) {
max = d;
min = *this;
flag = -1; // 小减大时结果为负
}
int n = 0;
while (min != max) {
++min; // 避免使用后置++,减少一次拷贝构造
++n;
}
return n * flag;
}
private:
int _year;
int _month;
int _day;
};
一个能够体现调用拷贝构造的小问题
// 问程序在运行中产生几个对象
int n = 0;
class C {
public:
C() {
++n;
}
C(const C& c) {
++n;
}
private:
int n;
};
//C f1(C b) { // 6
//C& f1(C b) { // 4
//C f1(C& b) { // 4
C& f1(C& b) { // 2 课件引用类型可以提高效率
return b;
}
explicit关键字
Date d1(1); //构造
// 如果想避免这类隐式类型转化可以在函数前加上“explicit”=》explicit Date(int year)
Date d2 = 2; //隐式类型转换 构造出tmp(2) + 在用tmp拷贝构造d2(tmp)+优化成直接构造
//const Date& d2 = 2;
Date d3 = d1; //拷贝构造
// C++11 新特性
Date d4(1, 2, 3);
// 同上通过在函数前加上“explicit”避免下面饮食转化
Date d5 = { 1, 2, 3 };
特性
-
静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区。
-
静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明。
-
类静态成员可用类名::静态成员或者对象.静态成员来访问。
-
静态成员函数没有隐藏的this指针,不能访问任何非静态成员。
-
静态成员也是类的成员,受public、protected、private访问限定符的限制。
问题:
静态成员函数可以调用非静态成员函数吗?不可以
非静态成员函数可以调用类的静态成员函数吗?可以
友元
友元提供了一种突破封装的方式,有时提供了便利。但是友元会增加耦合度,破坏了封装,所以 友元不宜多用。
友元分为:友元函数和友元类
class A {
private:
int a;
public:
friend void show(A& x);
};
void show(A& x) {
cout << x.a;
}
小结
-
什么是类?
类是一种将抽象转换为用户定义类型的C++工具,它将数据表示和操纵数据的方法组合成一个整洁的包。类提供了一种封装数据和操作这些数据的方法的机制,从而使得代码更模块化、可重用和易于维护。
-
类如何实现抽象、封装和数据隐藏?
抽象:通过类和对象来建模现实世界的实体,隐藏复杂的实现细节,暴露出简单的接口。
封装:将数据和操作数据的函数捆绑在一起,保护数据不被外界随意修改。
数据隐藏:通过访问控制(如
public
,private
,protected
)来限制对类内部数据的访问。 -
对象和类之间的关系是什么?
类是对象的蓝图或模版。对象是类的实例化结果。类定义了对象的属性和行为,而对象是类的具体实现。
-
除了是函数之外,类函数成员与类数据成员之间的区别是什么?
类数据成员(成员变量):存储对象的属性或状态。
类函数成员(成员函数):定义了对象的行为或操作数据的方法。
-
定义一个类来表示银行账户。数据成员包括储户姓名、账号(使用字符串)和存款。成员函数执行如下操作:
- 创建一个对象并将其初始化;
- 显示储户姓名、账号和存款;
- 存入参数指定的存款;
- 取出参数指定的款项;
class bank { public: bank(const string& name, const string& account, double deposit) :_name(name), _account(account), _deposit(deposit) { } void Show() { cout << "name: " << _name << endl; cout << "account: " << _account << endl; cout << "deposit: " << _deposit << endl; } void Save(int deposit) { _deposit += deposit; } void Withdraw(int deposit) { if (deposit <= _deposit) _deposit -= deposit; else cout << "你没有这么多钱~" << endl; } private: string _name; string _account; double _deposit; };
-
类构造函数在何时被调用?类析构函数呢?及其顺序
构造函数:在对象创建时调用,用于初始化对象。全局先定义,然后依次线性调用。
析构函数:在对象销毁时调用,用于清理资源。后定义的对象先析构(栈);局部对象先析构,全局对象静态对象再析构。
注:拷贝构造的优化问题(了解):
-
什么是默认构造函数,拥有默认构造函数有何好处?
默认构造函数:没有参数的构造函数。如果没有定义任何构造函数,编译器会自动生成一个默认构造函数。
好处:1. 允许创建没有参数的对象。
2. 方便数组和容器的初始化。
-
this 和 *this 是什么?
this:指向调用成员函数的对象的指针。
*this:解引用 this 指针,返回调用成员函数对象本身。