文章目录
- 一、前言
- 二、类和对象------中
-
- [2.1 默认成员函数](#2.1 默认成员函数)
- [2.2 构造函数](#2.2 构造函数)
-
- [2.2.1 顺序表的初始化](#2.2.1 顺序表的初始化)
- [2.2.2 Date类默认构造](#2.2.2 Date类默认构造)
- [2.2.3 Date类初始化构造](#2.2.3 Date类初始化构造)
-
- [2.2.3.1 无参构造](#2.2.3.1 无参构造)
- [2.2.3.2 全缺省构造](#2.2.3.2 全缺省构造)
- [2.2.4 构造函数的特点](#2.2.4 构造函数的特点)
- [2.3 析构函数](#2.3 析构函数)
-
- [2.3.1 顺序表的销毁](#2.3.1 顺序表的销毁)
- [2.3.2 Satck类和MyQueue类的析构](#2.3.2 Satck类和MyQueue类的析构)
- [2.3.3 析构函数的特点](#2.3.3 析构函数的特点)
- [2.4 拷贝构造函数](#2.4 拷贝构造函数)
-
- [2.4.1 Date日期类的拷贝构造](#2.4.1 Date日期类的拷贝构造)
- [2.4.2 Stack类和MyQueue类的拷贝构造](#2.4.2 Stack类和MyQueue类的拷贝构造)
- [2.4.3 拷贝构造函数的特点](#2.4.3 拷贝构造函数的特点)
- [2.5 运算符重载](#2.5 运算符重载)
-
- [2.5.1 重载规则](#2.5.1 重载规则)
- [2.5.2 赋值运算符重载](#2.5.2 赋值运算符重载)
-
- [2.5.2.1 operator==](#2.5.2.1 operator==)
- [2.5.3 取地址运算符重载](#2.5.3 取地址运算符重载)
-
- [2.5.3.1 const成员函数](#2.5.3.1 const成员函数)
- [2.5.3.2 取地址运算符重载的两种方式](#2.5.3.2 取地址运算符重载的两种方式)
- 三、总结
一、前言
学前三问------今天我还在学习吗?我是不是真的在努力学习?学习完之后有什么新的收获?古话说得好,每日三省吾身,我们也得当仁不让啊。今天咱们接着学习类和对象,再深入探讨类和对象,领悟C++的语法之美。
二、类和对象------中
2.1 默认成员函数
在C++中,我们在编写一个类的时候,编译器会默认生成6个成员函数,顾名思义------默认成员函数就是不显示实现出来的,而是编译器自动生成的。那么这6个成员函数分别是哪些呢?先让我们来看思维导图,如下:
但是,虽然编译器默认实现了6个成员函数,这也并不意味着皆大欢喜,仍然存在两个注意点:
1、默认输生成的成员函数的行为是什么,是否满足我们的需求?
2、如果不满足我们的需求,我们又该如何自己实现?
2.2 构造函数
在C语言数据结构------如顺序表学习的时候,我们都会自己写一个Init初始化函数,而在C++中,我们则需要使用构造函数,它的功能和Init一样,但是构造函数具有自动调用的特点,这一点比起手动调用初始化函数就有了完美的优势。
2.2.1 顺序表的初始化

2.2.2 Date类默认构造
cpp
//默认构造
#include <iostream>
using namespace std;
class Date
{
public:
/*Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}*/
Date()
{
_year;
_month;
_day;
}
void Print()
{
cout << _year << endl;
cout << _month << endl;
cout << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
d1.Print();
/*Date d2(2005, 05, 29);
d2.Print();*/
return 0;
}

2.2.3 Date类初始化构造
2.2.3.1 无参构造
cpp
//初始化
#include <iostream>
using namespace std;
class Date
{
public:
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
//Date()
//{
// _year;
// _month;
// _day;
//}
void Print()
{
cout << _year << endl;
cout << _month << endl;
cout << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
/*Date d1;
d1.Print();*/
Date d2(2005, 5, 29);
d2.Print();
return 0;
}

2.2.3.2 全缺省构造
cpp
#include <iostream>
using namespace std;
class Date
{
public:
Date(int year = 1, int month = 2, int day = 3)
{
_year = year;
_month = month;
_day = day;
}
/*Date()
{
_year;
_month;
_day;
}*/
void Print()
{
cout << _year << endl;
cout << _month << endl;
cout << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
d1.Print();
/*Date d2();
d2.Print();*/
return 0;
}

2.2.4 构造函数的特点
1、函数名与类名相同;
2、无返回值,也不需要写void;
3、对象实例化编译器会自动调用对应的构造函数;
4、若没有显示实现构造函数,则编译器会调用默认构造,反之则不会调用默认构造;
5、构造函数可以重载;
6、在C++语法上:无参构造,全缺省构造,以及我们不写构造时编译器默认生成的构造统称为默认构造。这三种构造函数只能存在一个,若同时存在可能会报错。
2.3 析构函数
在顺序表表中除了Init这个初始化函数必不可少之外,还有一个函数也必不可少------Destroy销毁。而在C++中则使用析构函数来实现销毁作用。
2.3.1 顺序表的销毁

2.3.2 Satck类和MyQueue类的析构
cpp
#include <iostream>
using namespace std;
class Stack
{
public:
Stack(int n = 4)
{
arr = (int*)malloc(sizeof(int) * 4);
if (arr == nullptr)
{
perror("malloc fail!");
return;
}
capacity = n;
top = 0;
}
~Stack()
{
cout << "~Stack" << endl;
if (arr)
free(arr);
capacity = 0;
top = 0;
}
private:
int* arr;
int capacity;
int top;
};
class MyQueue//MyQueue函数编译器会实现默认构造,析构也会自动调用Stack的析构函数
{
public:
private:
Stack pushst;
Stack popst;
};
int main()
{
Stack st;//实现一次析构
MyQueue mq;//实现两次析构
return 0;
}

2.3.3 析构函数的特点
1、析构函数名就是在类名前加~ ;
2、无参数无返回值,也不用加void;
3、一个类只能定义一个析构函数 ,若没有显示定义析构函数,编译器会调用默认的析构函数;
4、当对象的生命周期结束 的时候,编译器会自动调用析构函数 ;
5、编译器自动生成的析构函数对内置类型不做处理,自定义类型会调用自己的析构函数 ;
6、即使我们显示实现了析构函数,但是对于自定义类型成员还是会调用他自己的析构函数 ;
7、如果类中并没有申请资源,则可以不调用析构函数,如Date日期类,可以进行默认析构 (即在栈区上开辟一块空间,随着函数栈帧的结束自动销毁 ),但是Satck类和MyQueue类是在堆区上申请资源,需要自己实现析构函数,否则会出现资源泄露;
8、对于一个局部域的多个对象,后定义的先析构,但是注意:static修饰的对象会在局部域的其他成员析构之后最后再进行析构。
2.4 拷贝构造函数
2.4.1 Date日期类的拷贝构造
cpp
#include <iostream>
using namespace std;
class Date
{
public:
Date(int year = 1, 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;
}
//Date( Date d)
//{
// _year = d._year;
// _month = d._month;
// _day = d._day;
//}
Date(Date* d)
{
_year = d->_year;
_month = d->_month;
_day = d->_day;
}
void Print()
{
cout << _year << endl;
cout << _month << endl;
cout << _day << endl;
}
void Func1(Date d)
{
cout << &d << endl;
d.Print();
}
Date& Func2(Date d)//传引用返回
{
Date tmp(2025, 5, 20);
tmp.Print();
return tmp;
}
Date Func3(Date& d)//传值返回
{
Date tmp(2025, 5, 20);
tmp.Print();
return tmp;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2005, 5, 29);
Date d;
//d.Func1(d1);
//cout << &d1 << endl;
//Date d2(&d1);//这里传的是地址,只是普通的构造,不是拷贝构造
//d1.Print();
//d2.Print();
//Date d3(d1);//这里是拷贝构造
//d3.Print();
//Func2为传引用返回,随着函数的结束局部域对象被销毁,会出现野指针
Date ret1 = d.Func2(d1);
ret1.Print();
cout << endl;
//Func3为传值返回,传值返回会产生临时对象,把返回值存在临时对象中,由这个临时对象返回给接收值
Date ret2 = d.Func3(d1);
ret2.Print();
return 0;
}
2.4.2 Stack类和MyQueue类的拷贝构造
cpp
#include <iostream>
#include <assert.h>
using namespace std;
typedef int STDataType;
class Stack
{
public:
Stack(int n = 4)//普通构造
{
arr = (STDataType*)malloc(sizeof(STDataType) * 4);
if (arr == nullptr)
{
perror("malloc fail!");
return;
}
capacity = n;
top = 0;
}
Stack(const Stack& st)//拷贝构造
{
arr = (STDataType*)malloc(sizeof(STDataType) * st.capacity);
if (arr == nullptr)
{
perror("malloc fail!");
return;
}
top = st.top;
capacity = st.capacity;
}
void Push(int x)
{
if (capacity == top)
{
int newcapacity = 2 * capacity;
int* tmp = (STDataType*)realloc(arr, sizeof(STDataType) * newcapacity);
if (tmp == nullptr)
{
perror("realloc fail!");
return;
}
capacity = newcapacity;
arr = tmp;
}
arr[top++] = x;
}
int Front()
{
assert(top > 0);
return arr[top - 1];
}
~Stack()
{
cout << "~Stack" << endl;
if (arr)
free(arr);
arr = nullptr;
capacity = 0;
top = 0;
}
private:
STDataType* arr;
int capacity;
int top;
};
class MyQueue//MyQueue默认调用Satck的构造,析构域拷贝构造
{
public:
private:
Stack pushst;
Stack popst;
};
int main()
{
Stack st1;//析构1次
st1.Push(1);
st1.Push(2);
st1.Push(3);
st1.Push(4);
cout << st1.Front() << endl;
Stack st2 = st1;//如果没有拷贝构造,析构st1和st2的时候会析构同一块空间,发生报错
MyQueue mq1;//析构2次
MyQueue mq2;//析构2次
return 0;
}
2.4.3 拷贝构造函数的特点
1、拷贝构造函数是构造函数是一个重载。
2、拷贝构造的第一个参数必须是类类型对象的引用,使用传值方式编译器会直接报错,因为这在语法逻辑上会引起无穷递归调用。


3、C++规定自定义类型对象进行拷贝行为时都会进行拷贝构造,所以传值传参和传值返回都会调用拷贝构造。

4、若未显示定义拷贝构造,编译器会自动生成拷贝构造函数。自动生成的拷贝构造对内置类型会进行浅拷贝------一个字节一个字节的拷贝,对于自定义类型会调用它自己的拷贝构造。
5、传值返回和传值传参会产生一个临时对象调用拷贝构造,而传引用返回返回的是返回对象的别名,并没有产生拷贝。但是如果返回的是当前函数局部域的局部对象,当函数结束的时候,函数栈帧随之销毁,那么此时传引用返回的是一个野指针。传引用返回虽然可以减少拷贝,但是一定要确保返回对象在函数栈帧销毁之后还存在,以避免野指针的情况。


2.5 运算符重载
2.5.1 重载规则
1、当运算符被用于类类型的对象时,C++语法允许我们通过运算符重载的形式指定新的含义,并且语法规定类类型对象使用运算符时,必须使用运算符重载,否则会编译报错。
2、运算符重载是一个函数,它的名字由operator和后面定义的运算符共同组成。
3、运算符重载函数也具有返回类型、参数列表和函数体。4、运算符重载函数的参数个数和运算符作用的对象数量一样多。
5、如果一个重载运算符函数的成员函数,那么它的第一个运算对象默认传给this指针,因此运算符重载作为成员函数时,参数比成员对象少一个。
6、运算符重载之后,它的优先级和结合性与对应的内置类型运算符保持一致。
7、不能创建语法中没有的运算符,并且这五个运算符不支持重载。(.* :: sizeof ?: . )。
8、重载的运算符需要具有意义,不能无意义的重载运算符。
2.5.2 赋值运算符重载
2.5.2.1 operator==
我们在写一个类时,可能会在这个类中定义两个或更多的对象,但是如果我们直接使用运算符对这些对象进行运算的话,C++的语法是不支持的,这个时候我们就必须使用operator赋值运算符来进行运算符重载。例子如下:
2.5.3 取地址运算符重载
2.5.3.1 const成员函数
1.const修饰的成员函数称为const成员函数,const放在成员函数列表的后面。
2.const实际修饰的是成员函数隐含的this指针
如果我们对Print成员函数加上const修饰,那又会怎么样呢?
2.5.3.2 取地址运算符重载的两种方式
取地址运算符重载可以分为普通取地址运算符重载和const取地址运算符重载。但是对于这两个类型的重载,编译器自己生成的就够用了,一般不用自己显示实现取地址运算符的重载,除非在比较极端的场景:如我们不想让比如知道正确的地址,我们需要返回一个错误的地址。
三、总结
以上就是类和对象------中的全部内容了,相比于类和对象------上,这一节内容的难度可谓是直线上升了,有很多比较难的知识点,大家一定得下来好好消化,加油!
成功比的不是天赋,而是坚持
