02-类与对象(上篇)
类的分离定义
类定义了一个新的作用域,类的所有成员都在类的作用域中。在类体外定义成员时,需要使用::
作用域操作符指明成员属于哪个类域。
class Person
{
public:
void PrintPersonInfo();
private:
char _name[20];
char _gender[3];
int _age;
static int _count;
};
int Person::_count = 1;
// 这里需要指定PrintPersonInfo是属于Person这个类域
void Person::PrintPersonInfo()
{
cout << _name << " "<< _gender << " " << _age << endl;
}
问题:C++中struct和class的区别是什么?
解答:C++需要兼容C语言,所以C++中struct可以当成结构体使用。另外C++中struct还可以用来定义类。和class定义类是一样的,区别是struct定义的类默认访问权限是public,class定义的类默认访问权限是private。注意:在继承和模板参数列表位置,struct和class也有区别,后序给大家介绍。
类对象模型
类对象中存储成员变量,成员函数存储在公共代码段
计算类的大小:
// 类中既有成员变量,又有成员函数:4
class A1 {
public:
void f1(){}
private:
int _a;
};
// 类中仅有成员函数:1
class A2 {
public:
void f2() {}
};
// 类中什么都没有-空类:1
class A3
{};
结论:一个类的大小,实际就是该类中”成员变量”之和,当然要注意内存对齐
注意空类的大小,空类比较特殊,编译器给了空类一个字节来唯一标识这个类的对象。
this指针
一般人都知道成员函数中的变量是this指针指向的,所以可以不写
const修饰的this指针——const成员函数
将const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改,在纯输出的成员函数可以保护内部数据
问题:this指针存在哪里
解答:this的本质是一个隐藏的形参,形参在函数调用时压栈做临时变量,所以this指针存储在栈中。this指针会频繁调用,部分编译器将this指针优化存储在寄存器ECX中。
Date* ptr = nullptr;
//正常运行
ptr->func();
(*ptr).func();
//程序崩溃
ptr->Init(2022, 2, 2);
解释:
成员函数存储在公共代码段中,所以不需要解引用this指针,即使为空也能正常调用。
但当调用的成员函数中需要获取this指针指向的变量时,程序就会崩溃
03-类与对象(中篇)
类的6个默认成员函数
构造函数
构造函数的作用:不是开辟对象空间,而是创建对象后编译器自动调用来将对象赋初值的函数。
特征:函数名与类名相同、无返回值、无参数构造函数和全缺省参数构造函数只能有一个
问题:构造函数自己不定义,编译器也会自动生成构造函数,那为什么还要自己定义?
解答:编译器生成的构造函数非常鸡肋,默认给内置成员变量赋随机值(自定义成员变量调用它自己的构造函数)
//补丁:C++11支持在成员变量声明时可以给默认值
class Date
{
private:
// 基本类型(内置类型)
int _year = 1970;
int _month = 1;
int _day = 1;
// 自定义类型
Time _t;
};
析构函数
析构函数的作用是:当对象被编译器销毁时完成资源的清理工作。
特征:函数名是在类名之前加~、无返回值、析构函数唯一
拷贝构造函数
拷贝构造函数搭配作用是:以已有对象为模板创建新对象
特征:拷贝构造函数是构造函数的重载形式,参数只有const 类对象的引用,
拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用。
编译器生成的默认拷贝构造函数是浅拷贝,如果涉及到资源申请的必须自己写拷贝构造函数(malloc)
为了提高程序效率,一般对象传参时,尽量使用引用类型,返回时根据实际场景,能用引用尽量使用引用。
运算符重载
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
// bool operator==(Date* this, const Date& d2)
// 这里需要注意的是,左操作数是this,指向调用函数的对象
// 将重载函数直接封装在类的内部,参数中隐含this指针
bool operator==(const Date& d2)
{
return _year == d2._year;
&& _month == d2._month
&& _day == d2._day;
}
Date& operator=(const Date& d)
{
if(this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
private:
int _year;
int _month;
int _day;
};
赋值重载函数
1、编译器会自动生成默认的赋值重载函数,所以赋值运算符只能重载成类的成员函数而不能重载成全局函数
2、编译器自动生成的赋值重载函数是浅拷贝,涉及资源管理的函数必须自己手搓拷贝构造
前置++和后置++重载
//前置++
Date& operator++()
{
_day += 1;
return *this;
}
//后置++
Date operator++(int)
{
Date temp(*this);
_day += 1;
return temp;
}
// C++规定:后置++重载时多增加一个int类型的参数
// 注意:后置++是先使用后+1,因此需要返回+1之前的旧值,故需在实现时需要先将this保存
一份,然后给this+1
// temp是临时对象,因此只能以值的方式返回,不能返回引用
取地址及const取地址操作符重载
&和const &一般由编译器自动生成,不需要重载
04-类和对象(下篇)
再谈构造函数
前言:虽然上述构造函数调用之后,对象中已经有了一个初始值,但是不能将其称为对对象中成员变量的初始化,构造函数体中的语句只能将其称为赋初值,而不能称作初始化。因为初始化只能初始化一次,而构造函数体内可以多次赋值。
初始化列表
在构造函数之后以冒号开头,以逗号分割,每一个成员变量后面跟一个放在括号内的初始值
class Date
{
public:
Date(int year, int month,int day)
: _year(year)
, _month(month)
, _day(day)
{}
private:
int _year;
int _month;
int _day;
};
解释:const成员只能在初始化时赋初始值,一旦实例化无法改变
尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,一定会先使用初始化列表初始化。
static成员
static成员函数就是无需实例化对象就能调用的函数
static成员变量就是在所有对象中的值都一样的静态变量,静态成员变量必须在类外声明
class A
{
public:
A(int x)
{
cout << "A()" << endl;
}
~A()
{
cout << "~A()" << endl;
}
static void test()
{
_num++;
}
int getvalue()
{
return _num;
}
private:
static int _num;
};
int A::_num = 0;
int main()
{
A a1;
A a2;
cout << a1.getvalue() << endl;
cout << a2.getvalue() << endl;
A::test();
A::test();
A::test();
A::test();
A::test();
cout << a1.getvalue() << endl;
cout << a2.getvalue() << endl;
return 0;
}
输出:
A()
A()
0
0
5
5
~A()
~A()
【问题】
1. 静态成员函数可以调用非静态成员函数吗?
答:可以,但是静态成员函数不带this指针,需要在参数中传入类对象的地址,在静态函数内部通过地址解引用调用函数
2. 非静态成员函数可以调用类的静态成员函数吗?
答:显然可以
友元函数
引入:我们希望自定义cout输出类时的内容,需要重载operator <<
,如果将重载函数写在类的内部,则第一个参数默认为this指针,但是cout的使用习惯将第一个参数为cout,所以我们必须在函数外实现对cout的重载,同时还要访问类对象内的成员变量进行打印。
像这样,为了满足因为使用参数习惯要在类外部定义、同时要能访问类内部私有变量的函数,引入了友元函数
#include<iostream>
using namespace std;
class Date
{
friend ostream& operator<<(ostream& _cout, Date& _this);
public:
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void printline()
{
cout << _year << " " << _month << ' ' << _day << endl;
}
private:
int _year = 2000;
int _month = 1;
int _day = 1;
};
ostream& operator<<(ostream& _cout, Date& _this)
{
_cout << _this._year << " " << _this._month << ' ' << _this._day << endl;
return _cout;
}
int main()
{
Date d1 = {2024,2,17};
cout << d1 << endl;
return 0;
}
注意:友元函数只是能访问类成员变量的普通函数,不属于任何类的成员函数,但要在类中加friend函数声明
匿名对象
int main()
{
// 不能这么定义对象,因为编译器无法识别下面是一个函数声明,还是对象定义
//A aa1();
// 但是我们可以这么定义匿名对象,匿名对象的特点不用取名字,
// 但是他的生命周期只有这一行,我们可以看到下一行他就会自动调用析构函数
A();
A aa2(2);
// 匿名对象在这样场景下就很好用,当然还有一些其他使用场景,这个我们以后遇到了再说
Solution().Sum_Solution(10);
//只想使用成员函数,就可以借助匿名对象调用内部函数
return 0;
}