一.面向过程和面向对象初步认识
1.面向过程(C语言)
2.面向对象(C++)
二.类的定义
1.定义
class className
{
// 类体:由成员函数和成员变量组成
};
2.定义方式
![](https://i-blog.csdnimg.cn/direct/f76c11ba3a7040f2916d1feb14743512.png)
三.类的访问限定符与封装
1.访问限定符
2.封装
四.类的作用域
class Person
{
public:
void PrintPersonInfo();
private:
char _name[20];
char _gender[3];
int _age;
};
// 这里需要指定PrintPersonInfo是属于Person这个类域
void Person::PrintPersonInfo() //类域限定符
{
cout << _name << " "<< _gender << " " << _age << endl;
}
五.类的实例化
![](https://i-blog.csdnimg.cn/direct/d2a916b3e0c341e7a7a23860c767c5fe.png)
class Person
{
public:
void showInfo();
private:
char* _name;
char* _sex;
int age;
}
void Test()
{
Person man; //对象实例化
}
六.类对象模型
1.类对象的存储方式
2.结构体内存对齐规则
对齐数 = 编译器默认的一个对齐数(8) 与 该成员大小(1)(4)的较小值,两个对齐数1和4,最大对齐数为4,_a先占位4,_ch占位1,但是总大小为最大对齐数的整数倍,故而总大小为8。
3.为什么要内存对齐
左图是内存对齐规则下的存储方式,右图是不按照对齐规则的方式。可以看到,如果不按照对齐规则的话,_a需要被读取两次,然而在对齐规则下,_a只需要被读取一次,所以对齐规则的作用就是方便读取,保证读取都只需要一次。
七.this指针
1.this指针定义
2.this指针特性
![](https://i-blog.csdnimg.cn/direct/36117726a6c64ea9bcff1fcd4ce8f83b.png)
3.this指针的两个问题
// 下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
class A
{
public:
void Print()
{
cout << "Print()" << endl;
}
private:
int _a;
};
int main()
{
A* p = nullptr;
p->Print();
return 0;
}
// 下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
class A
{
public:
void PrintA()
{
cout<<_a<<endl;
}
private:
int _a;
};
int main()
{
A* p = nullptr;
p->PrintA();
return 0;
}
而上面这段代码就会导致程序崩溃了,因为cout<<_a<<endl;本质上是this->_a,对空指针进行了解引用操作,这会导致程序崩溃。
八.类的6个默认成员函数
![](https://i-blog.csdnimg.cn/direct/759cc855af4649f2bc7dbad262a3788d.png)
九.构造函数
1.定义
构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,以保证每个数据成员都有 一个合适的初始值,并且在对象整个生命周期内只调用一次。
2.特性
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;
};
void TestDate()
{
Date d1; // 调用无参构造函数
Date d2(2015, 1, 1); // 调用带参的构造函数
// 注意:如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明
// 意思就是不能写成Date d1();因为这样的话不能搞清是创建对象还是函数声明了
Date d3();
}
5. 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。仔细阅读main里面的注释:
class Date
{
public:
/*
// 如果用户显式定义了构造函数,编译器将不再生成
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
*/
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
// 将Date类中构造函数屏蔽后,代码可以通过编译,因为编译器生成了一个无参的默认构造函数
// 将Date类中构造函数放开,代码编译失败,因为一旦显式定义任何构造函数,编译器将不再生
成
// 无参构造函数,放开后报错:error C2512: “Date”: 没有合适的默认构造函数可用
Date d1;
return 0;
}
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
![](https://i-blog.csdnimg.cn/direct/1a9d6b2e872646fbb7603024ab9841bc.png)
![](https://i-blog.csdnimg.cn/direct/28c35bf50ff84b6783197c659f5a11c4.png)
class Date
{
public:
Date() //无参构造
{
_year = 1900;
_month = 1;
_day = 1;
}
Date(int year = 1900, int month = 1, int day = 1) //全缺省构造
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
// 以下测试函数能通过编译吗? 不可以,因为存在多个默认构造
void Test()
{
Date d1;
}
十.析构函数
1.定义
2.特性
~Stack() //有动态申请资源,需要手动写析构函数,必须写
{
cout << "~Stack()" << endl;
free(_a);
_a = nullptr;
_capacity = _top = 0;
}
~Date() //全是内置类型成员,不需要手动写析构函数,可以不写
{
cout << "~Date()" << endl;
_year = 0;
_month = 0;
_day = 0;
}
class MyQueue //全是自定义类型成员,且各自都已经手写了自己的默认析构,可以不写
{
private:
Stack _pushst;
Stack _popst;
};
十一.拷贝构造函数
1.定义
2.特性
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
// Date(Date d) // 错误写法:编译报错,会引发无穷递归
Date(const Date& d) // 正确写法
{
_year = d._year; //this->_year = d._year this是d2的别名,d是传进来的d1
_month = d._month;
_day = d._day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
Date d2(d1);
return 0;
}
![](https://i-blog.csdnimg.cn/direct/a407fcf476314f189f9edec4029daa53.png)
![](https://i-blog.csdnimg.cn/direct/d7229155656b46cab3dedb1c836c02df.png)
![](https://i-blog.csdnimg.cn/direct/0fa33e69471644bc8fe563aad3e216a7.png)
class Stack
{
public:
Stack(int capacity = 4)
{
cout << "Stack()" << endl;
_a = (int*)malloc(sizeof(int) * capacity);
if (nullptr == _a)
{
perror("malloc申请空间失败");
return;
}
_capacity = capacity;
_top = 0;
}
Stack(const Stack& st)
{
_a = (int*)malloc(sizeof(int) * st._capacity);
if (nullptr == _a)
{
perror("malloc申请空间失败");
return;
}
memcpy(_a, st._a, sizeof(int) * st._top);
_top = st._top;
_capacity = st._capacity;
}
~Stack()
{
cout << "~Stack()" << endl;
free(_a);
_a = nullptr;
_capacity = _top = 0;
}
private:
int* _a = nullptr;
int _top = 0;
int _capacity;
};
class MyQueue
{
private:
Stack _pushst;
Stack _popst;
};
int main()
{
// 必须自己实现,实现深拷贝
Stack st1;
Stack st2(st1);
return 0;
}
总结:类中如果没有涉及到申请资源,写不写拷贝构造都可以,一旦涉及到申请资源,必须写拷贝构造,否则就是浅拷贝。
十二.赋值运算符重载
1.运算符重载
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
Date(const Date& d)
{
cout << "Date(const Date& d)" << endl;
_year = d._year;
_month = d._month;
_day = d._day;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
//private: //如果不改为公有类外就不能调用
int _year;
int _month;
int _day;
};
bool operator<(const Date& x1, const Date& x2)
{
if (x1._year < x2._year)
{
return true;
}
else if (x1._year == x2._year && x1._month < x2._month)
{
return true;
}
else if (x1._year == x2._year && x1._month == x2._month && x1._day < x2._day)
{
return true;
}
return false;
}
int main()
{
Date d1(2023, 4, 26);
Date d2(2023, 6, 21);
cout << (d1 < d2) << endl; // 转换成operator<(d1, d2);
return 0;
}
于是引出来了类内的运算符重载,将函数放在类内,会出现下面的问题:
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
Date(const Date& d)
{
cout << "Date(const Date& d)" << endl;
_year = d._year;
_month = d._month;
_day = d._day;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
bool operator<(const Date& x)
{
if (_year < x._year)
{
return true;
}
else if (_year == x._year && _month < x._month)
{
return true;
}
else if (_year == x._year && _month == x._month && _day < x._day)
{
return true;
}
return false;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2023, 4, 26);
Date d2(2023, 6, 21);
cout << (d1 < d2) << endl; // 转换成d1.operator<(d2);
return 0;
}
2.赋值运算符重载
(1).运算符重载格式
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
Date(const Date& d)
{
cout << "Date(const Date& d)" << endl;
_year = d._year;
_month = d._month;
_day = d._day;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
// d4 = d1
// d1 = d1
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;
};
int main()
{
Date d1(2023, 4, 26);
Date d2(2023, 6, 21);
// 已经存在的两个对象之间复制拷贝 -- 运算符重载函数
d1 = d2;
return 0;
}
细节上,第一点和第二点:如果不使用引用直接传值,会使用多余的拷贝构造函数,一是传进来的时候会调用,而是返回的时候调用。
(2).赋值运算符只能重载成类的成员函数不能重载成全局函数
(3).用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。
3.前置++和后置++重载
#include<iostream>
using namespace std;
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
Date(const Date& d)
{
cout << "Date(const Date& d)" << endl;
_year = d._year;
_month = d._month;
_day = d._day;
}
// 前置++:返回+1之后的结果
// 注意: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; //temp传给d,this传给d1
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d;
Date d1(2022, 1, 13);
d = d1++; // d: 2022,1,13 d1:2022,1,14
d = ++d1; // d: 2022,1,15 d1:2022,1,15
return 0;
}
十三.const成员
![](https://i-blog.csdnimg.cn/direct/d1ecc55038ab47b5a33f4be16c28643f.png)
![](https://i-blog.csdnimg.cn/direct/27562f82009f4d568883dc887e226bf4.png)
![](https://i-blog.csdnimg.cn/direct/a7157b97338c483388a067a090d833b1.png)
十四.初始化列表
class Date
{
public:
Date(int year, int month, int day)
: _year(year)
, _month(month)
, _day(day)
{}
private:
int _year;
int _month;
int _day;
};
class A
{
public:
A(int a)
:_a(a)
{}
private:
int _a;
};
class B
{
public:
B(int a, int ref)
:_aobj(a)
,_ref(ref)
,_n(10)
{}
private:
A _aobj; // 没有默认构造函数
int& _ref; // 引用
const int _n; // const
};
![](https://i-blog.csdnimg.cn/direct/68989d463754459789bc633d5984c7ee.png)
class A
{
public:
A(int a)
:_a1(a)
,_a2(_a1)
{}
void Print() {
cout<<_a1<<" "<<_a2<<endl;
}
private:
int _a2;
int _a1;
};
int main() {
A aa(1);
aa.Print();
}
A.输出1 1
B.程序崩溃
C.编译不通过
D.输出1 随机值
上面这道题,因为类内成员声明顺序是先_a2再_a1,所以初始化的顺序也是先_a2再_a1,在初始化_a2时,_a1是未被初始化的随机值,所以_a2是随机值,_a1是1。
为了避免上面的情况(尤其是涉及到动态申请空间),我们可以像下面这样定义,比如说stack用如下方法初始化就更加合理:
class Stack
{
public:
Stack(int capacity = 10)
: _a((int*)malloc(capacity * sizeof(int))) //这里_capacity还没有被初始化,是随机值
, _top(0)
, _capacity(capacity)
{
if (nullptr == _a)
{
perror("malloc申请空间失败");
exit(-1);
}
// 要求数组初始化一下
memset(_a, 0, sizeof(int) * capacity);
}
private:
int* _a;
int _top;
int _capacity;
};
int main()
{
Stack st;
return 0;
}
十五.隐式类型转换与explicit关键字
1.隐式类型转换
class A
{
public:
A(int a)
:_a(a)
{
cout << "A(int a)" << endl;
}
A(const A& aa)
:_a(aa._a)
{
cout << "A(const A& aa)" << endl;
}
private:
int _a;
};
int main()
{
A aa1(1);
A aa2 = 2; // 隐式类型转换,整形转换成自定义类型
// 2构造一个A的临时对象,临时对象再拷贝构造aa2 -->优化用2直接构造
// error C2440: “初始化”: 无法从“int”转换为“A &”
//A& aa3 = 2;
const A& aa3 = 2;
return 0;
}
2.explicit关键字
如果不希望有隐式类型转换的出现,可以用explicit修饰构造函数,这将会禁止构造函数的隐式转换。
十六.static成员
1.概念
class A
{
public:
A() { ++_scount; }
A(const A& t) { ++_scount; }
~A() { --_scount; }
static int GetACount()
{
return _scount;
}
private:
static int _scount; //_scount成员变量在静态区,不在类内,所以他的改变相当于是有记忆的
};
int A::_scount = 0;
void TestA()
{
cout << A::GetACount() << endl; //0
A a1, a2; //+1 +1
A a3(a1); //+1
cout << A::GetACount() << endl; //3
}
int main()
{
TestA();
return 0;
}
2.特性
class Sum {
public:
Sum()
{
_ret += _i;
++_i;
}
static int GetSum()
{
return _ret;
}
private:
static int _i; //变量和包含它的函数都要使用静态成员
static int _ret;
};
int Sum::_ret = 0;
int Sum::_i = 1;
class Solution {
public:
int Sum_Solution(int n) {
Sum s[n]; //n个对象
return Sum::GetSum();
}
};
3.两个问题
this
指针,因此不能直接调用非静态成员函数,因为非静态成员函数需要一个实例对象来调用。静态成员函数只能访问静态数据成员和其他静态成员函数。
#include <iostream>
using namespace std;
class MyClass {
public:
static void SetValue(int val) {
_value = val; // 访问静态数据成员
}
static void PrintValue() {
cout << "Value: " << _value << endl; // 访问静态数据成员
}
private:
static int _value; // 静态数据成员
};
// 定义静态数据成员
int MyClass::_value = 0;
int main() {
MyClass::SetValue(10); // 调用静态成员函数来设置静态数据成员
MyClass::PrintValue(); // 调用静态成员函数来打印静态数据成员
return 0;
}
访问其他静态成员函数:
#include <iostream>
using namespace std;
class MyClass {
public:
static void FunctionA() {
cout << "FunctionA is called" << endl;
FunctionB(); // 调用另一静态成员函数
}
static void FunctionB() {
cout << "FunctionB is called" << endl;
}
};
int main() {
MyClass::FunctionA(); // 调用静态成员函数FunctionA,它内部调用FunctionB
return 0;
}
类名::静态成员函数
或直接调用静态成员函数。(如上面累加的代码)。
十七.友元函数
1.友元函数
#include <iostream>
using namespace std;
class Date
{
friend ostream& operator<<(ostream& _cout, const Date& d);
friend istream& operator>>(istream& _cin, Date& d);
public:
Date(int year = 1900, int month = 1, int day = 1)
: _year(year)
, _month(month)
, _day(day)
{}
private:
int _year;
int _month;
int _day;
};
ostream& operator<<(ostream& _cout, const Date& d)
{
_cout << d._year << "-" << d._month << "-" << d._day;
return _cout;
}
istream& operator>>(istream& _cin, Date& d)
{
_cin >> d._year;
_cin >> d._month;
_cin >> d._day;
return _cin;
}
int main()
{
Date d;
cin >> d;
cout << d << endl;
return 0;
}
2.友元类
#include <iostream>
using namespace std;
class Time
{
friend class Date;
// 声明日期类为时间类的友元类,则在日期类中就直接访问Time类中的私有成员变量
public:
Time(int hour = 0, int minute = 0, int second = 0)
: _hour(hour)
, _minute(minute)
, _second(second)
{}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
: _year(year)
, _month(month)
, _day(day)
{}
void SetTimeOfDate(int hour, int minute, int second)
{
// 直接访问时间类私有的成员变量
_t._hour = hour;
_t._minute = minute;
_t._second = second;
}
private:
int _year;
int _month;
int _day;
Time _t;
};
十八.内部类
1.概念
2.特性
![](https://i-blog.csdnimg.cn/direct/f24dda9fba8c4ba7b8ded76e4d289a5f.png)
class A
{
private:
static int k;
int h;
public:
class B // 内部类是外部类的天生友元
{
public:
void foo(const A& a)
{
cout << k << endl;//OK
cout << a.h << endl;//OK
}
private:
int b;
};
};
int A::k = 1;
int main()
{
A::B b;
b.foo(A()); //这里构造了一个A类的匿名对象
return 0;
}
对上面那道牛客题,我们可以进行改进,使用内部类:
class Solution {
public:
class Sum {
public:
Sum() {
_ret += _i;
++_i;
}
};
int Sum_Solution(int n) {
Sum s[n];
return _ret;
}
private:
static int _i;
static int _ret;
};
int Solution::_ret = 0;
int Solution::_i = 1;
十九.匿名对象
错误写法:对象和函数名冲突,不能清晰分辨
Solution sl();
sl.Sum_Solution(10);
有名对象
Solution sl;
sl.Sum_Solution(10);
匿名对象(创建一个临时性的对象)
Solution().Sum_Solution(20);
匿名对象:1.生命周期在当行;2.匿名对象具有常性;3.const引用可以延长其生命周期
class A
{
public:
A(int a = 0)
:_a(a)
{
cout << "A(int a)" << endl;
}
~A()
{
cout << "~A()" << endl;
_a = 0;
}
private:
int _a;
};
class Solution {
public:
int Sum_Solution(int n) {
cout << "Sum_Solution" << endl;
//...
return n;
}
};
int main()
{
A aa(1); // 有名对象 -- 生命周期在当前函数局部域 -- 形参
A(2); // 匿名对象 -- 生命周期在当前行 -- 实参
//A& ra = A(1); // 匿名对象具有常性
const A& ra = A(1); // const引用延长匿名对象的生命周期,生命周期在当前函数局部域
Solution().Sum_Solution(20);
return 0;
}
上面代码输出的结果:
二十.对象拷贝编译器优化问题(了解)
#include<iostream>
using namespace std;
class A
{
public:
A(int a = 0)
:_a1(a)
{
cout << "A(int a)" << endl;
}
A(const A& aa)
:_a1(aa._a1)
{
cout << "A(const A& aa)" << endl;
}
A& operator=(const A& aa)
{
cout << "A& operator=(const A& aa)" << endl;
if (this != &aa)
{
_a1 = aa._a1;
}
return *this;
}
~A()
{
cout << "~A()" << endl;
}
private:
int _a1 = 1;
};
void f1(A aa)
{}
A f2()
{
A aa;
return aa;
}
int main()
{
// 传值传参
A aa1;
f1(aa1);
cout << endl;
// 隐式类型,连续构造+拷⻉构造->优化为直接构造
f1(1);
// ⼀个表达式中,连续构造+拷⻉构造->优化为⼀个构造
f1(A(2));
cout << endl;
cout << "***********************************************" << endl;
// 传值返回
// 返回时⼀个表达式中,连续拷⻉构造+拷⻉构造->优化⼀个拷⻉构造 (vs2019)
// ⼀些编译器会优化得更厉害,进⾏跨⾏合并优化,直接变为构造。(vs2022)
f2();
cout << endl;
// 返回时⼀个表达式中,连续拷⻉构造+拷⻉构造->优化⼀个拷⻉构造 (vs2019)
// ⼀些编译器会优化得更厉害,进⾏跨⾏合并优化,直接变为构造。(vs2022)
A aa2 = f2();
cout << endl;
// ⼀个表达式中,连续拷⻉构造+赋值重载->⽆法优化
aa1 = f2();
cout << endl;
return 0;
}
![](https://i-blog.csdnimg.cn/direct/58217e5a579246cb9a61937a927d1bf1.png)