类和对象
# 类的定义
class className {
//类体:由成员变量和成员函数组成
}; //注意后面的分号
2
3
通常情况,声明放在头文件(.h
)中,定义放在源文件(.cpp
)中
声明
#pragma once
#include<iostream>
using namespace std;
class person {
private:
int age;
string name;
public:
person(string name,int age);
void showInfo();
};
2
3
4
5
6
7
8
9
10
11
12
定义
#define _CRT_SECURE_NO_WARNINGS 1
#include"person.h"
void person::showInfo() {
cout << "姓名:" << name << endl;
cout << "年龄:" << age << endl;
}
person::person(string name,int age) {
this->age = age;
this->name = name;
}
2
3
4
5
6
7
8
9
10
11
12
# 类的封装
# 访问限定符
public
private
protected
注意:
public修饰的成员可以在类外直接被访问。
protected和private修饰的成员在类外不能直接被访问。
访问权限从该访问限定符出现的位置开始直到下一个访问限定符出现时为止。
class的默认访问权限为private,struct为public(因为struct要兼容C)
# 类的内存计算
- 如果类中有属性,则内存大小等于所有属性内存总和,运用结构体的内存对齐规则
- 如果没有属性,则还有
1字节
,表示占位,即这个空类时存在的 - 静态成员变量不属于类对象上
- 成员函数不属于类对象上
# this指针
C++中通过引入this指针解决该问题:C++编译器给每个“非静态的成员函数”增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有成员变量的操作,都是通过该指针去访问的。只不过所有操作对用户是透明的,即用户不需要来传递,而是编译器自动完成。
# 默认构造函数
# 构造函数
类名(){}
- 函数没有返回值
- 函数名和类名一致
- 构造函数可以有参数,可以发生重载
- 系统自动调用,且调用一次
- 编译器自动生成的构造函数机制: 1、编译器自动生成的构造函数对内置类型不做处理。 2、对于自定义类型,编译器会再去调用它们自己的默认构造函数。
- 无参的构造函数、全缺省的构造函数以及我们不写编译器自动生成的构造函数都称为默认构造函数,并且默认构造函数只能有一个
- **如果类中没有显示定义构造函数,则****C++**编译器会自动生成一个无参的默认构造函数,若用户显示定义了,则编译器就不再生成
- 当创建一个类的时候,C++都会添加三个函数
- 无参构造函数
- 无参析构函数
- 拷贝构造函数
另外:
- 当用户定义了有参构造函数,则系统不再提供无参构造函数,但仍会提供拷贝函数
- 当用户定义了拷贝构造函数,则系统不再提供其他构造函数
构造函数的主要任务并不是开空间创建对象,而是初始化对象。
person::person(string name,int age) {
this->age = age;
this->name = name;
}
person::person(string name) {
this->name = name;
}
person::person(int age) {
this->age = age;
}
person::person() {
cout << "无参构造函数~" << endl;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
person p1("zhangsan");
person p2("zhangsan",22);
person p3(22);
person p4();
2
3
4
一般推荐使用全缺省的构造函数(注意全缺省的声明和定义不能同时带默认值)
person(string name = " ", int age = 0);
person::person(string name, int age) {
this->age = age;
this->name = name;
}
2
3
4
5
6
# 列表初始化
在C++11中,C++新增了一种语法:初始化列表
以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个成员变量后面跟一个放在括号中的初始值或表达式
person::person(string _name, int _age) :
age(age),
name(name) {}
2
3
注意事项:
- 每个成员变量在列表初始化中只能出现一次
- 类中包含以下成员时,必须使用列表初始化来初始化:
- 引用成员变量
- const修饰的成员变量
- 没有默认构造函数的自定义类型成员变量
class A {
private:
int a;
public:
A(int _a = 0) : a(_a) {
cout << "A的初始化" << endl;
}
};
class demo {
private:
const int a;
int& b;
A aa;
public:
demo(int _a = 0, int _b = 0, int _aa = 0) :
a(_a),
b(_b),
aa(_aa) {}
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
- 成员变量在类中声明的次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后顺序无关
# explicit关键字
构造函数不仅可以构造和初始化对象,对于单个参数的构造函数,还支持隐式类型转换
// 对于一个peson类,我们还可以这样新建一个对象
person p = {"zjhangsan",11};
2
这种定义方式叫做隐式类型转换,等价于两句代码:
person tmp("zhangsan",11); // 先构造
Date d1(tmp); // 再拷贝构造
2
但是这种代码的可读性不是很好,我们若是想禁止单参数构造函数的隐式转换,可以用关键字explicit
来修饰构造函数。
explicit person(string _name = " ", int _age = 0);
# 析构函数
作用于对象销毁前,系统自动调用,执行清理工作
~类名(){}
析构函数无返回值
函数名和类名一致,前面加
~
构造函数不可以有参数,不可以发生重载
系统在对象销毁前自动调用,且调用一次
编译器自动生成的析构函数机制:
1、编译器自动生成的析构函数对内置类型不做处理。
2、对于自定义类型,编译器会再去调用它们自己的默认析构函数。
- 先构造的后析构,后构造的先析构(因为对象是定义在函数中的,函数调用会建立栈帧,栈帧中的对象构造和析构也要符合先进后出的原则)
# 拷贝构造函数
拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用从const
修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。
- 拷贝构造函数是构造函数的一个重载形式
因为拷贝构造函数的函数名也与类名相同
- 拷贝构造函数的参数只有一个且必须使用
引用传参
,使用传值方式会引发无穷递归调用
person::person(const person& p) {
this->name = p.name;
this->age = p.age;
}
//...
int main() {
person p1("zhangsna", 22);
//拷贝构造
person p2(p1);
person p3 = p2;
person().getname();
// 匿名对象,生命周期就是这一行,无需创建对象,知识为了调用类中的函数
p2.showInfo();
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 取地址操作符重载
class Date
{
public:
Date* operator&()// 取地址操作符重载
{
return this;
}
const Date* operator&()const// const取地址操作符重载
{
return this;
}
private:
int _year;
int _month;
int _day;
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 运算符重载
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,其目的就是让自定义类型可以像内置类型一样可以直接使用运算符进行操作。
例如:
class person {
private:
int age;
string name;
public:
person(string name = " ", int age = 0);
person(const person& p);
~person();
void showInfo();
};
int main() {
person p1("zhangsna", 22);
person p2 = p1;
p1 == p2; // error
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
运算符重载提供了新的思路,运算符重载,帮助我们解决这种问题
函数原型:返回值 operator运算符(参数列表)
注意:
不能通过连接其他符号来创建新的操作符:比如operator@
重载操作符必须有一个类类型或枚举类型的操作数。
用于内置类型的操作符,重载后其含义不能改变。
作为类成员的重载函数时,函数有一个默认的形参this,限定为第一个形参。
sizeof 、:: 、.* 、?: 、.
这5个运算符不能重载
bool person::operator==(const person& p) {
return age == p.age && name == p.name;
}
2
3
# 赋值运算符重载
person& person::operator=(const person& p) {
if (this != &p) {
name = p.name;
age = p.age;
}
return *this;
}
2
3
4
5
6
7
int main() {
person p1("zhangsan", 22);
person p2("zhangsia", 25);
p2 += 11;
person p3 = p1 = p2;
p3.showInfo();
return 0;
}
2
3
4
5
6
7
8
9
10
区分一下拷贝构造函数和赋值运算符重载
p2 = p1; // 赋值
person p3(p1); //拷贝构造
person p4 = p2; //拷贝构造
2
3
# 浅拷贝和深拷贝
接下来,当我们把类中的拷贝构造函数和赋值运算符重载注释掉后,发现程序没有报错。
int main() {
person p1("zhangsan", 22);
person p2("lisi", 23);
p2 = p1; // 赋值
person p3(p1); // 拷贝构造
person p4 = p2; // 拷贝构造
p1.showInfo();
p2.showInfo();
p3.showInfo();
p4.showInfo();
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
原因在于以下两点:
我们不实现构造函数和析构函数时,编译器自动生成默认构造函数和析构函数,并且针对成员变量,内置类型不做处理,自定义类型会调用该成员变量地构造和析构
我们不实现operator=时,编译器自动生成拷贝构造和赋值重载,并且完成按字节的值拷贝(浅拷贝)
浅拷贝通过一个一个字节赋值来实现的,我们发现person这个类就可以不用写复制拷贝和operator=,通过默认的就可以完成任务。那么我们什么时候需要自己来写呢?
例如,像栈(Stack)这样的类,编译器自动生成的拷贝构造函数就不能满足我们的需求了:
因此,在这种情况下,会出现对同一块空间释放多次的问题,我们要自己写拷贝构造函数。
# const修饰
# 修饰成员变量
被const修饰的成员变量称为常对象
常对象的属性也是不能修改的,并且常对象只能调用常函数
# 修饰成员函数
成员函数 + const 表示 const 类名* this
常函数this指针的指向和指向的值均不能修改
void person::showInfo() const { // -> void person::showInfo(const person* this)
cout << "姓名:" << name << endl;
cout << "年龄:" << age << endl;
}
2
3
4
拓展:
int a = 100;
int b = 200;
const int* ptr1 = &a;
int const* ptr2 = &a;
// const在*之前:限制*p,不能使用*p改变内容
ptr1 = &b; // right
ptr2 = &b; // right
*ptr1 = 300; // error
*ptr2 = 300; //error
int* const ptr3 = &a;
// const在*之后:限制p,不能使用p改变内容
ptr3 = &b; // error
*ptr3 = 300; // right
2
3
4
5
6
7
8
9
10
11
12
13
14
15
思考下面几个问题(经典面试题):
const对象可以调用非const成员函数吗?-> 不可以
非const对象可以调用const成员函数吗?-> 可以
const成员函数内可以调用其他的非const成员函数吗?-> 不可以
非cosnt成员函数内可以调用其他的cosnt成员函数吗?-> 可以
(权限只能缩小,不能放大)
# static修饰
声明为static的类成员称为类的静态成员。用static修饰的成员变量,称之为静态成员变量;用static修饰的成员函数,称之为静态成员函数。静态成员变量一定要在类外进行初始化
静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区,可以直接使用类名::成员名来使用
# 修饰成员变量
当static成员变为共有的时候:
#include <iostream>
using namespace std;
class Test {
public:
static int _n; //公有
};
// 静态成员变量的定义初始化
int Test::_n = 0;
int main() {
Test test;
cout << test._n << endl; //1.通过类对象突破类域进行访问
cout << Test()._n << endl; //3.通过匿名对象突破类域进行访问
cout << Test::_n << endl; //2.通过类名突破类域进行访问
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
当static成员变为私有时:
#include <iostream>
using namespace std;
class Test {
public:
static int GetN() {
return _n;
}
private:
static int _n;
};
// 静态成员变量的定义初始化
int Test::_n = 0;
int main() {
Test test;
cout << test.GetN() << endl; //1.通过对象调用成员函数进行访问
cout << Test().GetN() << endl; //2.通过匿名对象调用成员函数进行访问
cout << Test::GetN() << endl; //3.通过类名调用静态成员函数进行访问
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 修饰成员函数
静态成员函数不可以调用非静态成员函数
非静态成员函数可以调用静态成员函数
# 友元
类似于:朋友到你家做客,他可以到客厅,但是不能到你的卧室。但是对于部分亲密的朋友,你允许进入你的卧室,这就类似于友元的作用
友元分为友元函数和友元类。友元提供了一种突破封装的方式,有时提供了便利。但是友元会增加耦合度,破坏了封装,所以友元不宜多用。
# 友元函数
在类中friend + 函数声明
就表示这个函数是你的朋友
虽然友元打破了封装,但是有一种情况我们必须使用友元
int a = 100;
cout << a;
2
我们能否像内置类型那样使用cout直接打印出来呢?这是我们想到可以用重载<<
运算符
void person::operator<<(ostream& out) {
cout << name << "-" << age;
}
person p("zhangsan", 33);
p << cout;
2
3
4
5
6
7
这样调用的话,可读性太差,我们需要另一种方式改进,知识我们可以使用友元
friend void operator<<(ostream& out, const person& p);
//...
void operator<<(ostream& out, const person& p) {
cout << p.name << "-" << p.age;
}
cout << p;
2
3
4
5
6
7
但是我们在使用cout时有可能是连续输出的
cout << i << j << endl;
我们可以联想连续赋值,返回ostream即可
ostream& operator<<(ostream& out, const person& p) {
cout << p.name << "-" << p.age;
return out;
}
//...
person p1("zhangsan", 33);
person p2("lisi", 23);
cout << p1 << p2;
2
3
4
5
6
7
8
9
同理,我们还可以用友元来写输入流
ostream& operator<<(ostream& out, const person& p) {
out << p.name << "-" << p.age;
return out;
}
istream& operator>>(istream& in, person& p) {
in >> p.name >> p.age;
return in;
}
2
3
4
5
6
7
8
9
注意:
友元函数可以访问类是私有和保护成员,但不是类的成员函数。
友元函数不能用const修饰。
友元函数可以在类定义的任何地方声明,不受访问限定符的限制。
一个函数可以是多个类的友元函数。
友元函数的调用与普通函数的调用原理相同。
# 友元类
类似于友元函数,友元类可以访问另一个类的私有成员
友元类说明:
- 友元关系是单向的,不具有交换性。 例如上述代码中,B是A的友元,所以在B类中可以直接访问A类的私有成员变量,但是在A类中不能访问B类中的私有成员变量。
- 友元关系不能传递。如果A是B的友元,B是C的友元,不能推出A是C的友元。
# 实现Date类
// 日期类
class Date {
private:
int year;
int month;
int day;
// 获取月份天数
int montharr[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
int getMonthday(int _year, int _month) {
if (_month == 2 && (((_year % 4 == 0) && (_year % 100 != 0)) || ((_year % 100 == 0) && (_year % 400 == 0))))
// 闰年
return 29;
return montharr[_month];
}
public:
// 构造函数
Date(int _year = 0, int _month = 0, int _day = 0) {
if (_month > 0 && _day > 0 && _year > 0) {
year = _year;
month = _month;
day = _day;
} else {
cout << "日期非法!" << endl;
}
}
// 打印日期
void printDate();
// +/- 日期
Date &operator-=(int _day);
Date &operator+=(int _day);
Date operator+(int _day);
Date operator-(int _day);
Date &operator--();
Date &operator++();
// 日期比较
bool operator>(const Date &d);
bool operator>=(const Date &d);
bool operator<(const Date &d);
bool operator<=(const Date &d);
bool operator==(const Date &d);
bool operator!=(const Date &d);
// 日期 - 日期 : 返回相差天数
int operator-(const Date& d);
};
void Date::printDate() {
cout << year << "年" << month << "月" << day << "日" << endl;
}
// 如何调整 + 日期?
// 1.若日已满,则日减去当前月的天数,月加一。
// 2.若月已满,则将年加一,月置为1。
// 反复执行1和2,直到日期合法为止。
Date &Date::operator+=(int _day) {
// 负数:复用operator-=
if (_day < 0) {
*this -= _day;
} else {
day += _day;
int curmonth = getMonthday(year, month);
while (day > curmonth) {
day -= curmonth;
month++;
if (month > 12) {
year++;
month = 1;
}
}
}
return *this;
}
Date Date::operator+(int _day) {
Date temp(year, month, day);
temp += _day;
return temp;
}
Date &Date::operator++() {
*this += 1;
return *this;
}
// 如何调整 - 日期:
// 1.若日为负数,则月减一。
// 2.若月为0,则年减一,月置为12。
// 3.日加上当前月的天数。
Date &Date::operator-=(int _day) {
if (_day < 0) {
*this += -day;
} else {
day -= _day;
while (day < 0) {
month--;
day += getMonthday(year, month);
if (month < 1) {
year--;
month = 12;
}
}
}
return *this;
}
Date Date::operator-(int _day) {
Date temp(*this);
temp -= _day;
return temp;
}
Date &Date::operator--() {
*this -= 1;
return *this;
}
bool Date::operator==(const Date &d) {
return year == d.year && month == d.month && day == d.day;
}
bool Date::operator>(const Date &d) {
if (year > d.year)
return true;
else {
if (month > d.month)
return true;
else {
if (day > d.month)
return true;
}
}
return false;
}
bool Date::operator>=(const Date &d) {
return *this > d || *this == d;
}
bool Date::operator<(const Date &d) {
if (year < d.year)
return true;
else {
if (month < d.month)
return true;
else {
if (day < d.month)
return true;
}
}
return false;
}
bool Date::operator<=(const Date &d) {
return *this < d || *this == d;
}
bool Date::operator!=(const Date &d) {
return !(*this == d);
}
int Date::operator-(const Date& d) {
Date min = d;
Date max = *this;
int n = 0;
if(*this < d) {
min = *this;
max = d;
}
while (min != max) {
++min;
n++;
}
return n;
}
int main() {
Date date1(2024, 12, 4);
Date date2(2025, 1, 13);
cout << (date1 > date2) << endl;
date1 -= 12;
cout << (date1 - date2);
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
通过实现日期类,能够更加了解C++的默认函数和函数重载。
# from cen