一、类的定义
1. 类定义注意事项
- 以下面代码为例,class 是定义类的关键字,Stack 为类名,类中的内容为类的成员,类的变量为或成员变量,类中的函数为类的方法或成员函数
- C++ 兼容 C 语言的 struct 用法,因此 C++ 也可以用 struct 定义类,相比于 C 语言的 struct,C++ 的 struct 里可以定义函数,类中的成员函数默认为 inline
详细解析了 C++ 类与对象的核心概念,涵盖类定义、访问限定符、实例化及内存对齐规则。重点讲解了 this 指针的作用机制、默认成员函数(构造函数、析构函数、拷贝构造、赋值重载、取地址重载)的实现细节与注意事项。此外,还介绍了类型转换、static 静态成员、友元机制、内部类、匿名对象以及编译器在对象拷贝时的优化策略。内容旨在帮助开发者深入理解 C++ 面向对象编程的基础与进阶知识。

class Stack { public: void Init() //成员函数 { //... } private: int* _arr; int _top; }; //注意要带分号 struct Stack //struct 定义类,不需要 typedef { void Init() { //... } int* _arr; int _top };
class Stack { public: void Init(); //成员函数声明 private: int* _arr; int _top; };
void Stack::Init(int n) //声明和定义分离时要指定类域 { //... }
#include <iostream>
using namespace std;
class Date //Date 类不占空间
{
public:
void Init(int year,int month,int day)
{
_year=year;
_month=month;
_day=day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1; //Date 类实例化出对象 d1
d1.Init(2025,2,7);
return 0;
}
void Init(Date* const this, int year, int month, int day)
#include <iostream>
using namespace std;
class A
{
public:
void Print()
{
cout << "A::Print()" << endl;
}
private:
int _a;
};
int main()
{
A* p = nullptr;
p->Print();
return 0;
}
#include <iostream>
using namespace std;
class A
{
public:
void Print()
{
cout << "A::Print()" << endl;
cout << _a << endl;
}
private:
int _a;
};
int main()
{
A* p = nullptr;
p->Print();
return 0;
}
this 指针一般存储在内存的栈区
面向对象编程的三大特性:封装、继承、多态
C++ 实现类的时候,将数据和函数都放到了类里面,通过访问限定符进行修饰,不能随意修改数据,这正是封装的体现
初始化:构造函数 销毁:析构函数 拷贝复制:拷贝构造 重载:赋值重载 重载:取地址重载
用于对象实例化时初始化对象
总结:构造函数多数情况下都需要自己去实现,少数情况如类中只有自定义类型且自定义类型有默认构造函数时(如:MyQueue,即包含两个 Stack),可以不写
#include <iostream>
using namespace std;
class Date
{
public:
// 1.⽆参构造函数
Date()
{
_year = 1;
_month = 1;
_day = 1;
}
// 2.带参构造函数 (此时与无参的构成重载)
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
// 3.全缺省构造函数 (与无参的不能同时存在)
/*Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}*/
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1; //调用默认构造函数
Date d2(2025,2,7); //调用带参构造函数
return 0;
}
用于销毁对象
#include <iostream>
using namespace std;
typedef int STDataType;
class Stack
{
public:
Stack(int n = 4)
{
_a =(STDataType*)malloc(sizeof(STDataType) * n);
if (nullptr == _a)
{
perror("malloc 申请空间失败");
return;
}
_capacity = n;
_top = 0;
}
~Stack()
{
cout << "~Stack()" << endl;
free(_a);
_a = nullptr;
_top = _capacity = 0;
}
private:
STDataType* _a;
size_t _capacity;
size_t _top;
};
// 两个 Stack 实现队列
class MyQueue
{
public:
//编译器默认⽣成 MyQueue 的析构函数调⽤了 Stack 的析构,释放的 Stack 内部的资源
// 显⽰写析构,也会⾃动调⽤ Stack 的析构
private:
Stack pushst;
Stack popst;
};
如果一个构造函数的第一个参数是自身类类型的引用,那么该构造函数称为拷贝构造函数
拷贝构造第一个参数必须是类类型对象的引用,如果传值,会引发无穷递归,它可以有多个参数,但第一个参数必须是类类型对象的引用,后面的参数必须有缺省值
#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;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
void Func1(Date d)
{
cout << &d << endl;
d.Print();
}
Date& Func2()
{
Date tmp(2024, 7, 5);
tmp.Print();
return tmp;
}
int main()
{
Date d1(2024, 7, 5); // C++ 规定⾃定义类型对象进⾏拷⻉⾏为必须调⽤拷⻉构造,所以这⾥传值传参要调⽤拷⻉构造
// 所以这⾥的 d1 传值传参给 Func 中 d 要调⽤拷⻉构造完成拷⻉,传引⽤传参可以减少这⾥的拷⻉
Func1(d1);
cout << &d1 << endl; // 这⾥可以完成拷⻉,但是不是拷⻉构造,只是⼀个普通的构造
Date d2(&d1);
d1.Print();
d2.Print(); //这样写才是拷⻉构造,通过同类型的对象初始化构造,⽽不是指针
Date d3(d1);
d2.Print(); // 也可以这样写,这⾥也是拷⻉构造
Date d4 = d1;
d2.Print(); // Func2 返回了⼀个局部对象 tmp 的引⽤作为返回值
// Func2 函数结束,tmp 对象就销毁了,相当于了⼀个野引⽤
Date ret = Func2();
ret.Print();
return 0;
}
赋值运算符重载是一个默认成员函数,用于两个已经存在的对象的拷贝。区分:拷贝构造用于一个对象初始化给另一个要创建的对象
#include <iostream>
using namespace std;
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
// 此处 this 指针为 const Date* const this
void Print() const
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
// 非 const 对象 d1 也可以调用 const 成员函数是一种权限的缩小
Date d1(2024, 7, 5);
const Date d2(2024, 8, 5);
return 0;
}
取地址运算符重载分为普通取地址运算符重载和 const 取地址运算符重载,⼀般这两个函数不需要自己去显⽰实现
初始化列表中按照成员变量在类中声明顺序进⾏初始化,跟成员在初始化列表出现的的先后顺序⽆关
#include <iostream>
using namespace std;
//Time 类
class Time
{
public:
Time(int hour) :_hour(hour)
{
cout << "Time()" << endl;
}
private:
int _hour;
};
//Date 类
class Date
{
public:
Date(int& x, int year = 1, int month = 1, int day = 1) :_year(year) ,_month(month) ,_day(day) ,_t(12) ,_ref(x) ,_n(1) {}
private:
int _year;
int _month;
int _day;
//以下三者必须在初始化列表初始化
Time _t; //自定义类型,无默认构造
int& _ref; //引用
const int _n; //const 类型
};
int main()
{
int i = 0;
Date d1(i);
return 0;
}
#include <iostream>
using namespace std;
class A
{
public:
//前面加 explicit 就不再支持类型转换
A(int a1) :_a1(a1) {}
A(int a1, int a2) :_a1(a1) ,_a2(a2) {}
void Print()
{
cout << _a1 << " " << _a2 << endl;
}
int Get() const
{
return _a1 + _a2;
}
private:
int _a1 = 1;
int _a2 = 2;
};
class B
{
public:
B(const A& a) :_b(a.Get()) {}
private:
int _b = 0;
};
int main()
{
//这实质上是先用 1 创建一个 A 的临时对象,再拷贝给 aa1,编译器会将这种连续构造 + 拷贝直接优化成直接构造
A aa1 = 1;
aa1.Print();
const A& aa2 = 1; //多参数转化
A aa3 = { 1,1 };
// aa3 隐式类型转换为 b 对象
B b = aa3;
const B& rb = aa3;
return 0;
}
#include <iostream>
using namespace std;
class A
{
private:
// 类内声明
static int _scount;
};
// 类外初始化
int A::_scount = 0;
#include <iostream>
using namespace std;
//前置声明
class B;
class A
{
// 友元声明
friend void func(const A& aa, const B& bb);
private:
int _a1 = 1;
int _a2 = 2;
};
class B
{
// 友元声明
friend void func(const A& aa, const B& bb);
private:
int _b1 = 3;
int _b2 = 4;
};
void func(const A& aa, const B& bb)
{
cout << aa._a1 << endl;
cout << bb._b1 << endl;
}
int main()
{
A aa;
B bb;
func(aa, bb);
return 0;
}
#include <iostream>
using namespace std;
class A
{
private:
static int _x;
int _y = 1;
public:
//A 的内部类 B,B 是 A 的友元
class B
{
public:
void foo(const A& a)
{
cout << _x << endl;
cout << a._y << endl;
}
int _b1;
};
};
int A::_x = 1;
int main()
{
cout << sizeof(A) << endl; //通过类域创建 B 的对象
A::B b;
A aa;
b.foo(aa);
return 0;
}
class A
{
public:
A(int a = 0) :_a(a) {}
~A() {}
private:
int _a;
};
class Solution
{
public:
int Sum_Solution(int n)
{
//省略过程
return n;
}
};
int main()
{
A aa1; //A aa1(); 这种定义是错的,会与函数声明冲突
//匿名对象可以像这样定义,由于它的生命周期只有一行,因此定义之后到了下一行就会自动析构
A();
A(1);
A aa2(2); // 匿名对象在这样场景下就很好用
Solution().Sum_Solution(10);
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; // 传值返回
// 不优化的情况下传值返回,编译器会⽣成⼀个临时对象去拷⻉返回对象,临时对象作为函数调用表达式的返回值
// ⼀些编译器会优化得厉害,将构造的局部对象和拷贝构造的临时对象优化为直接构造(vs2022 debug)
f2();
cout << endl; // 返回时⼀个表达式中,连续拷⻉构造 + 拷⻉构造->优化⼀个拷⻉构造(vs2019 debug)
// ⼀些编译器会优化得更厉害,进⾏跨⾏合并优化,将构造的局部对象 aa 和拷⻉的临时对象和接收返回值对象 aa2 优化为⼀个直接构造。(vs2022 debug)
A aa2 = f2();
cout << endl; // ⼀些编译器会优化得更厉害,进⾏跨⾏合并优化,将构造的局部对象 aa 和拷⻉临时对象合 并为⼀个直接构造(vs2022 debug)
aa1 = f2();
cout << endl;
return 0;
}

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online