前言:上一遍博客类和对象学习笔记(1)是对类和对象的初步学习,本篇博客继续对C++中的类和对象的概念及用法进行深入学习。
类和对象学习笔记
1.类的6个默认成员函数
一个类中如果什么都没有
,可以把它称作空类
。
class A1
{}
但是不管是空类还是非空类
,每个类中总有默认的6个成员函数
:
构造
函数拷贝构造
函数析构
函数赋值操作符重载
取地址操作符重载
const修饰的取地址操作符重载
2.构造函数
a.构造函数概念
构造函数
是一个特殊的成员函数,名字与类名相同
,创建类类型对象时由编译器自动调用
,保证每个数据成员都有 一个合适的初始值
,并且在对象
的生命周期内只调用一次
。
b.构造函数的作用是什么?
class Tearch
{
public:
char* _name;
char* _num;
int _age;
public:
void setVal(char* name,char* num,int age) {
_name = name;
_num = num;
_age = age;
}
void sleep()
{
cout << _name << "老师正在睡觉" << endl;
}
};
对于上边的Tearch
类,我们创建对象
可以根据setVal
公有方法来初始化对象
,但是每次创建对象
的时候,我们都需要去调用setVal
方法去初始化。构造器就是为了解决这一问题,我们可以在类内实现一个构造器,每次创建对象的时候,由编译器自动的调用构造器来初始化对象,我们只需要传参即可。
c.构造函数的特性
函数名与类名
相同无返回值
- 在
实例化对象
时,由编译器自动调用
- 构造函数
支持重载
class Tearch
{
public:
char* _name;
char* _num;
int _age;
public:
//公有设置对象属性的的方法
void setVal(char* name,char* num,int age) {
_name = name;
_num = num;
_age = age;
}
//无参数构造函数
Tearch()
{}
//带参数构造函数
Tearch(char* name, char* num, int age)
{
_name = name;
_num = num;
_age = age;
}
void sleep()
{
cout << _name << "老师正在睡觉" << endl;
}
};
void testTearch()
{
//调用无参构造
Tearch t;
//调用带参数构造
Tearch t1("zhangsan", "123", 25);
//调用无参构造函数后边不能带()
//下边这代表声明一个函数,该函数返回值为Tearh引用类型
Tearch t2();
}
- 构造函数可以在
类中定义
,也可以在类外定义
class Tearch
{
public:
char* _name;
char* _num;
int _age;
public:
//类内声明
Tearch(char* name, char* num, int age);
void sleep()
{
cout << _name << "老师正在睡觉" << endl;
}
};
//类外定义,::为类域限定符
Tearch::Tearch(char* name, char* num, int age)
{
_name = name;
_num = num;
_age = age;
}
-
如果类中
没有显式定义构造函数
,则C++编译器会自动生成一个无参的默认构造函数
,一旦用户显式定义编译器将不再生成
-
无参的构造函数
和全缺省的构造函数
都称为缺省构造函数
,并且缺省构造函数只能有一个
class Tearch
{
public:
char* _name;
char* _num;
int _age;
public:
//公有设置对象属性的的方法
void setVal(char* name, char* num, int age) {
_name = name;
_num = num;
_age = age;
}
//无参数构造函数
//Tearch()
//{}
//下边是全缺省的构造函数,和上边和无参构造器只能存在一个
Tearch(char* name = "zhansan", char* num = "123", int age = 10)
{
_name = name;
_num = num;
_age = age;
}
void sleep()
{
cout << _name << "老师正在睡觉" << endl;
}
};
3.析构函数
a.析构函数的概念
析构函数
与构造函数
功能相反
,在对象被销毁
时,由编译器自动调用
,完成类的一些资源清理和汕尾工作
,但析构函数体内不是删除对象,而是做一些对象删除前的相关清理工作。
b.析构函数的特性
析构函数名
是在类名前加上字符 ~
无参数无返回值
- 一个类
有且只有一个析构函数
。若未显式定义
,系统会自动生成默认的析构函数
- 对象
生命周期结束
时,C++编译系统系统自动调用析构函数
构造函数
是顺着构造d1\d2\d3
(栈的后进先出特性),析构函数
倒着析构d3\d2\d1
- 应用:当构造函数在堆上开辟空间,就必须显示定义析构函数释放堆上开辟的空间。
class Tearch
{
public:
char* _name;
char* _num;
int _age;
public:
//构造函数存在堆上开辟的空间
Tearch(char* num = "123", int age = 10)
{
_name = (char*)malloc(10);
_num = num;
_age = age;
}
//析构函数必须显示的释放malloc开辟的空间
~Tearch()
{
free(_name);
_name = NULL;
_num = "\0";
_age = 0;
}
};
4.拷贝构造函数
a.拷贝构造函数的概念
只有单个形参
,该形参是对本类类型对象的引用(一般常用const修饰)
,在用已存在的类类型对象创建新对象时由编译器自动调用
。用来创建与某对象一模一样的新对象
b.拷贝构造函数的特性
拷贝构造函数
是构造函数
的一个重载形式
拷贝构造函数
的参数
只有一个
且必须使用引用传参
,使用传值方式会引发无穷递归调用
因为我们知道形参是实参的一份临时拷贝,在传参的过程中已经发生了一份临时拷贝,后边也会一直引起拷贝构造的。
class Tearch
{
public:
char* _name;
char* _num;
int _age;
public:
Tearch(char* name,char* num = "123", int age = 10)
{
_name = name
_num = num;
_age = age;
}
Tearch(Tearch& t)
{
_name = t._name;
_age = t._age;
_num = t._num;
}
//析构函数
~Tearch()
{
free(_name);
_name = NULL;
_num = "\0";
_age = 0;
}
};
void TestTearch()
{
Tearch t1;
Tearch t2(t1);//调用拷贝构造
}
- 若
未显示定义
,系统会默认生成拷贝构造函数
。默认的拷贝构造函数会按照成员的声明顺序依次拷贝类成员进行初始化
注:关于拷贝构造的一些面试:https://blog.csdn.net/qq_26437925/article/details/52125844
5.赋值运算符重载
a.运算符重载的概念
运算符重载
是具有特殊函数名的函数
,也具有其返回值类型
,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似
,函数名字为:关键字operator后面接需要重载的运算符符号
。
+
运算符重载:
class Date
{
private:
int _year;
int _month;
int _day;
public:
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
void SetDate(int year, int month, int day){
this->_year = year;
this->_month = month;
this->_day = day;
}
//无参数构造
Date()
{
_year = 2018;
_month = 11;
_day = 10;
}
//带参数构造,可以支持重载
Date(int year, int month, int day){
_year = year;
_month = month;
_day = day;
}
//拷贝构造函数
Date(Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
//日期类加上一个天数,运算符重载
Date operator+(int day)
{
Date tmp(*this);
tmp._day += day;
return tmp;
}
};
b.赋值运算符重载
- 赋值一次
class Date{
public:
Date(int year = 1990, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
//拷贝构造函数
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
//赋值运算符重载
void operator=(const Date& d)
{
if (this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2018, 20, 28);
//调用拷贝构造
Date d2 = d1;
//调用拷贝构造
Date d3(d1);
//调用赋值运算符重载
Date d4;
d4 = d1;
//调用赋值运算符重载,this为成员函数第一个隐藏的参数
Date d5;
d5.operator=(d1);
return 0;
}
- 连续赋值
//赋值运算符重载(支持连续赋值)
Date& operator=(const Date& d)
{
if (this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
赋值运算符应该注意以下几点:
- 参数类型为引用类型,减少一次拷贝
- 返回值为引用类型
- 检查是否自己给自己赋值
- 返回*this,this指向当前对象
- 一个类如果没有显式定义赋值运算符重载,编译器也会生成一个,完成值的拷贝工作
面试题:那些运算符不能重载?
.*(成员指针运算符)、::(作用域运算符)、sizeof、?:(三目运算符)、.(类属关系运算符)
- 重载的运算操作数至少有一个是自定义数据
- 只能重载已有的运算符
- 所有运算符保持其优先级和结合
6.默认拷贝构造与赋值运算符重载的问题
typedef int DataType;
class SeqList
{
public:
SeqList(int capacity = 10)
{
_pData = (DataType*)malloc(capacity * sizeof(DataType));
assert(_pData);
_size = 0;
_capacity = capacity;
}
//析构函数
~SeqList()
{
if (_pData)
{
//释放堆上的空间
free(_pData);
_pData = NULL;
_capacity = 0;
_size = 0;
}
}
private:
int* _pData;
size_t _size;
size_t _capacity;
};
void Test()
{
SeqList s1(10);
//调用编译器默认的拷贝构造(浅拷贝-值拷贝)
//只是简单的把指针赋值,s2和s1的_pData指向同一块空间
SeqList s2(s1);
//调用默认赋值运算符重载
//也是浅拷贝,s3和s2的_pData也是指向同一块空间
SeqList s3;
s3 = s2;
}
对于上边的问题,在调用默认的拷贝构造和赋值运算符重载是没问题的,但是在析构的时候就会出现问题
,因为3个对象
的_pData
指向同一块空间,析构的时候对同一块空间释放了3次
,这种拷贝应该使用深拷贝而不是浅拷贝
。
7.const关键字
const
修饰普通变量
:C++
中,const修饰的变量
已经为一个常量
,具有宏的属性
,即在编译期间
,编译器会将const所修饰的常量进行替换
void Test1()
{
const int a = 10;
//a = 100;a为常量,不能直接修改
//但是可以利用它的地址更改
int* pa = (int*)&a;
*pa = 100;
cout << a << endl;//10
cout << *pa << endl;//100
}
- const修饰类的成员函数:将const修饰的类成员函数称之为
const成员函数
,const修饰类成员函数
,实际修饰该成员函数隐含的this指针
,表明在该成员函数中不能对类的任何成员进行修改
。
class A
{
.....
void function()const; //常成员函数, 它不改变对象的成员变量. 也不能调用类中任何非const成员函数。
}
注:注意:const成员函数中一定要修改某个成员变量
,可在该成员变量前加 mutable
关键字
-
const修饰成员变量:表示
该成员变量不能被修改
,同时它只能在初始化列表中初始化
-
const修饰指针
const char *p;
char const *p;//const修饰的是指针所指向的内容,表示指向的内容不能被修改
char* const p;
const char* p;//const修饰的是指针,表示指针不能修改
const char* const p;//const既修饰指针也修饰指针指向的内容,都不能被修改
- const修饰引用参数
void function(const Class& Var);//引用参数在函数内不可以改变
void function(const TYPE& Var); //引用参数在函数内为常量不可变
- const修饰
类对象/对象指针/对象引用
:const修饰类对象表示该对象为常量对象,其中的任何成员都不能被修改
。对于对象指针和对象引用也是一样
。const修饰的对象
,该对象的任何非const成员函数都不能被调用
,因为任何非const成员函数会有修改成员变量的企图
。
面试题:
1.const修饰的对象可以访问const修饰的成员函数,不可以访问非const成员函数
2.非const对象可以访问const修饰的成员函数,也可以访问非const修饰的成员函数
3. const成员函数内可以调用其它的const成员函数,但是不能调用非const成员函数
4.非const成员函数内可以调用其它的const成员函数和非const成员函数