一、类
类的定义
class className
{
// 类体:由成员函数和成员变量组成
}; // 一定要注意后面的分号
类中的元素称为类的成员:类中的数据称为类的属性或者成员变量; 类中的函数称为类的方法或者成员函数。
定义类的关键字有两个:
class : class定义的类,成员访问权限默认为private
struct :struct定义的类,成员访问权限默认为public
【注意】:
1、类中成员函数声明和定义分开实现时,函数的定义需要通过域作用限定符::找到指定的类。指定类和::写在函数名前面,而不是返回类型前。
2、声明定义同时在类里面实现的函数会被默认加上inline。
类的访问限定符及封装
面向对象的三大特性:封装、继承、多态。
封装:隐藏对象的属性和方法的实现细节,仅通过对外公开接口来和对象进行交互。
C++实现封装的方式:用类将对象的属性与方法结合在一起,通过访问权限选择性地将接口提供给外部的用户使用。
【访问限定符说明】:
- public修饰的成员在类外可以直接被访问;
- protected和private修饰的成员在类外不能直接被访问,两者的区别仅体现在继承;
- 访问限定符的作用范围:从该访问限定符出现的位置开始到下一个访问限定符出现时为止;
- class的默认访问权限为private,struct为public(因为struct要兼容C)。
类的作用域:类定义了一个新的作用域,类的所有成员都在类的作用域中。在类外面定义成员,需要使用 :: 作用域解析符指明成员属于哪个类域。
类的实例化
类的实例化 <=> 定义一个类对象 =>才开辟空间
类写出来后没有实例化定义对象时仅仅是个声明,没有开辟空间,所以不能直接使用类里public的变量和函数。 类就像是设计房子的图纸,实例化就是按照图纸去建造房子。
类对象的存储方式
类中的成员变量储存在对象中,成员函数则储存在公共的代码段,静态成员储存在静态区也不在对象中。即,对象中只存储非静态成员变量。
对象所占的内存大小: 类中非静态成员变量经内存对齐后的大小。
【注意】:空类的大小为1字节,编译器给了空类一个字节来唯一标识这个类。
二、this指针
C++编译器给每个非静态成员函数都增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有成员变量的操作都是通过该指针去访问,这个指针就是this指针。
this指针的特性:
-
this指针:类类型* this ,每个非静态成员函数的第一个形参都是隐含的this指针;
-
this指针本质上是成员函数的一个形参,是对象调用成员函数时将对象地址作为实参传递给this形参,所以this指针不存储在对象里,而是存在函数的栈帧中;
-
this指针不能在形参中显示写出来,也不能实参显示传递,一般都是由编译器通过寄存器自动传递,但是可以在函数内显示使用。
由于this指针的存在,我们知道了成员函数(以下均指非静态成员函数)为什么只能通过对象才能够调用,而成员函数也是对对象进行处理,this指针的存在体现了C++面向对象的特点,同时也实现了封装。
三、默认成员函数
每个类中都存在6个默认成员函数,这些默认成员函数我们不能主动实现时编译器会自动生成。
构造函数
构造函数的主要任务是初始化对象,而不是创建对象。
构造函数的特性:
- 函数名与类名相同。
- 无返回值。
- 对象实例化时编译器自动调用对应的构造函数。
- 构造函数可以重载。
class Date
{
public:
// 1.无参构造函数
Date()
{}
// 2.带参构造函数
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。
编译器自动生成的构造函数对内置类型不做处理,对自定义类型会去调用它自己的构造函数。
初始化列表: 以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个成员变量后面跟一个放在括号中的初始值或表达式。
Date(int year, int month, int day)
: _year(year)
, _month(month)
, _day(day)
{}
初始化列表是对象成员定义的位置。构造函数建议均使用初始化列表初始化对象,而函数体内部可以用来做检查,赋值等列表干不了的活。
【注意】:
- 每个成员变量在初始化列表中只能出现一次(因为初始化只能初始化一次)
- 类中包含以下成员,必须放在初始化列表位置进行初始化:
引用成员变量
const成员变量
自定义类型成员(且该类没有默认构造函数时) - 成员变量在类中的声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关,所以建议初始化列表的定义顺序与类成员变量的声明顺序保持一致。
构造函数的实现及使用:
- 一般情况下,类中有内置类型成员,就需要自己实现构造函数,不能使用编译器自动生成的;
- 类中全部都是自定义类型成员,可以使用编译器自动生成的,不用自己实现;
- 默认成员函数的参数也是可以传缺省值的
默认构造函数: 不传参就可以调用的构造函数,包括:无参构造函数,全缺省构造函数和编译器自动生成的构造函数。一个类中只能有一个默认构造函数。
【注意】:C++11 中针对内置类型成员不初始化的缺陷,又打了补丁,即:内置类型成员变量在类中声明时可以给缺省值。
析构函数
析构函数是在对象销毁时会自动调用去完成对象中资源的清理工作。
析构函数的特性:
- 析构函数名是在类名前加上字符 ~。
- 无参数无返回值类型。
- 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载。
- 对象生命周期结束时,C++编译系统会自动调用析构函数。
默认析构函数对内置类型不做处理,对自定义类型会去调用它的析构函数,不过没有动态申请内存内置类型本来就不用处理,会随着栈区销毁而释放。
析构函数的实现:
- 一般情况下,有动态申请内存就需要显示实现析构函数来释放资源;
- 没有动态申请内存,只有栈区上的成员,不用写析构;
- 需要释放资源的成员都是自定义类型,不用写析构。
~Stack() //示例
{
if (_array)
{
free(_array);
_array = nullptr;
_capacity = 0;
_size = 0;
}
}
拷贝构造函数
拷贝构造函数:用于同类对象初始化创建对象。
浅拷贝:按内存存储的字节序完成拷贝。
拷贝构造函数的特性:
- 拷贝构造函数是构造函数的一个重载形式。
- 拷贝构造函数的参数只有一个且必须是类类型对象的引用(常用const修饰),使用传值方式编译器直接报错,因为会引发无穷递归调用。
- 类中如果没有涉及资源申请,拷贝构造函数是否写都可以;一旦涉及到资源申请,则拷贝构造函数一定要自己实现,否则就是浅拷贝。
C++语法规定了,自定义类型对象的拷贝必须要调用拷贝构造来完成拷贝。若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对内置类进行浅拷贝,对自定义类型会去调用它的拷贝构造。
// Date(const Date d) // 错误写法:编译报错,会引发无穷递归
Date(const Date& d) // 正确写法
{
_year = d._year;
_month = d._month;
_day = d._day;
}
String(const String& s) //拷贝构造 -- 深拷贝
: _str(new char[strlen(s._str) + 1])
{
strcpy(_str, s._str);
}
拷贝构造函数典型调用场景:
- 使用已存在对象创建新对象
- 函数参数类型为类类型对象
- 函数返回值类型为类类型对象
赋值重载
运算符重载
C++引入了运算符重载,运算符重载是具有特殊函数名的函数,同样具有返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
函数名为:关键字operator后面接需要重载的运算符符号。
函数声明:返回值类型 operator操作符(参数列表);
【注意】:
- 不能通过连接其他符号来创建新的操作符:比如operator@
- 重载操作符必须有一个类类型参数
- 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不能改变其含义
- 作为类成员函数重载时,其形参看起来比操作数数目少1个,因为成员函数的第一个参数为隐藏的this
.* :: sizeof ?: . 以上5个运算符不能重载。
// 这里需要注意的是,左操作数是this,指向调用函数的对象
bool operator==(const Date& d) // bool operator==(Date* this, const Date& d)
{
return _year == d._year;
&& _month == d._month
&& _day == d._day;
}
赋值重载
赋值运算符重载格式:
- 参数类型:const T&,传递引用可以提高传参效率
- 返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
- 检测是否自己给自己赋值的情况
- 返回*this :要符合连续赋值
Date& operator=(const Date& d) //示例
{
if(this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
【注意】:赋值运算符只能重载成类的成员函数不能重载成全局函数,因为类内未显式实现编译器还会自动在生成一个,就会冲突。
前置++和后置++
// 前置++
// 注意:this指向的对象函数结束后不会销毁,故以引用方式返回提高效率
Date& operator++()
{
_day += 1;
return *this;
}
// 前置++和后置++都是一元运算符,为了让前置++与后置++形成能正确重载
// C++规定:后置++重载时多增加一个int类型的参数,但调用函数时该参数不用传递,编译器自动传递
// 注意:后置++是先使用后+1,因此需要返回+1之前的旧值,故需在实现时需要先将this保存一份,然后给this+1
//而temp是临时对象,因此只能以值的方式返回,不能返回引用
// 后置++:
Date operator++(int)
{
Date temp(*this);
_day += 1;
return temp;
}
前置- - 和 后置- - 同理。