跳到主要内容
C++ 类与对象完全解析 | 极客日志
C++
C++ 类与对象完全解析 综述由AI生成 详细解析了 C++ 类与对象的核心概念,涵盖类定义、访问限定符、实例化及内存对齐规则。重点讲解了 this 指针的作用机制、默认成员函数(构造函数、析构函数、拷贝构造、赋值重载、取地址重载)的实现细节与注意事项。此外,还介绍了类型转换、static 静态成员、友元机制、内部类、匿名对象以及编译器在对象拷贝时的优化策略。内容旨在帮助开发者深入理解 C++ 面向对象编程的基础与进阶知识。
静心 发布于 2026/3/27 更新于 2026/6/5 33 浏览一、类的定义
1. 类定义注意事项
以下面代码为例,class 是定义类的关键字,Stack 为类名,类中的内容为类的成员,类的变量为或成员变量,类中的函数为类的方法或成员函数
C++ 兼容 C 语言的 struct 用法,因此 C++ 也可以用 struct 定义类,相比于 C 语言的 struct,C++ 的 struct 里可以定义函数,类中的成员函数默认为 inline
class Stack { public : void Init ()
2. 访问限定符
访问限定符有三种:public,private,protected
public 修饰的成员可以在外部直接被访问,protected 和 private 修饰的不能
class 内的成员默认为 private,struct 内的成员默认为 public
3. 类域
在类外定义定义成员时需要使用'类名::'指明成员属于哪个类域
class Stack { public : void Init () ;
void Stack::Init (int n)
二、实例化
1. 定义
用类在内存中创建对象的过程称为实例化
类是一种抽象描述,不占用空间,只有实例化出对象时,才有空间
#include <iostream>
using namespace std;
class Date
{
public :
void Init
{
_year=year;
_month=month;
_day=day;
}
:
_year;
_month;
_day;
};
{
Date d1;
d ( , , );
;
}
(int year,int month,int day)
private
int
int
int
int main ()
1.
Init
2025
2
7
return
0
2. 对象大小
类实例化的对象也要符合内存对齐的规则
使用内存对齐的原因:编译器一次会读取对齐数个字节,减少访问次数,以提高效率,这是一种以空间换时间的方法
内存对齐规则:
第一个成员在与结构体偏移量为 0 的位置
其他成员变量要对齐到对齐数的整数倍的地址处
对齐数=min(编译器默认对齐数,该成员大小)
VS 默认对齐数为 8
结构体总大小一定是最大对齐数(min(所有变量类型的最大值,默认对齐参数))的整数倍
空类实例化的对象大小为 1 字节
三、this 指针
编译器编译时,类的成员函数都会在形参的第一个位置,增加一个当前类类型的指针,叫做 this 指针,如 Date 类的 Init 函数:
void Init(Date* const this, int year, int month, int day)
成员函数中访问成员变量,都是通过 this 指针访问的
不能在函数中显示地写 this(编译器会自己处理),但可以在函数中显示地使用 this
看以下代码
#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 ;
}
该程序会正常运行
p 给 this,this 会找到 Print 函数的地址,执行 Print 函数,并不是解引用
#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 ;
}
该程序会运行崩溃
p 给 this,因此 this 为 nullptr,this 在第二句 cout 中会解引用访问_a
面向对象编程的三大特性:封装 、继承 、多态
C++ 实现类的时候,将数据和函数都放到了类里面,通过访问限定符进行修饰,不能随意修改数据,这正是封装的体现
四、类的默认成员函数
1. 默认成员函数 初始化:构造函数
销毁:析构函数
拷贝复制:拷贝构造
重载:赋值重载
重载:取地址重载
2. 构造函数
该函数名与类名相同
无返回值(也不需要写 void)
对象实例化时会自动调用对应的构造函数
构造函数可以重载
如果没有显式的构造函数,编译器会自动生成一个无参的构造函数,但显式定义后编译器将不再生成
不传参就可以调用 的构造函数称为默认构造 (包括无参构造函数,全缺省构造函数,编译器自动生成的构造函数)
对于自定义类型成员变量,要调用它的默认构造函数,如果没有默认构造就会报错,要初始化它需要用初始化列表来解决
总结:构造函数多数情况下都需要自己去实现,少数情况如类中只有自定义类型且自定义类型有默认构造函数时(如:MyQueue,即包含两个 Stack),可以不写
#include <iostream>
using namespace std;
class Date
{
public :
Date ()
{
_year = 1 ;
_month = 1 ;
_day = 1 ;
}
Date (int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private :
int _year;
int _month;
int _day;
};
int main ()
{
Date d1;
Date d2 (2025 ,2 ,7 ) ;
return 0 ;
}
3. 析构函数
析构函数名是在类名之前加字符~
无参数无返回值(无需 void)
一个类只有一个析构函数,如果没有显式定义,系统会生成默认的析构函数
对象生命周期结束时,系统会自动调用析构函数
与构造函数类似,不写时编译器自动生成的析构函数对内置成员不做处理,自定义类型会调用它的析构函数
显式写析构函数时,自定义成员依然会调用它的析构函数,即自定义成员无论如何都会调用它的析构函数
类中没有申请资源时,析构函数可以不写;但有申请资源时,一定要写析构函数,否则会造成内存泄漏
一个局部域的多个对象,总是后定义的先析构
#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;
};
class MyQueue
{
public :
private :
Stack pushst;
Stack popst;
};
4. 拷贝构造函数 如果一个构造函数的第一个参数是自身类类型的引用,那么该构造函数称为拷贝构造函数
拷贝构造函数是构造函数的一个重载
自定义类型对象进行拷贝时必须调用拷贝构造,所以自定义类型传值传参和传值返回都会调用拷贝构造
若未显式定义拷贝构造,编译器会自动生成拷贝构造函数,对内置类型成员变量会完成值拷贝/浅拷贝(逐字节拷贝),对自定义类型成员变量会调用它的拷贝构造
如果类必须要显式实现析构函数释放资源,那么它也必须要显式写拷贝构造函数
传值返回会产生一个临时对象去调用拷贝构造;传引用返回,返回的是对象的引用,不会产生拷贝,可以减少拷贝次数。但是传引用返回一定要确保返回对象在函数结束后还存在,才能用引用返回
拷贝构造第一个参数必须是类类型对象的引用 ,如果传值,会引发无穷递归,它可以有多个参数,但第一个参数必须是类类型对象的引用,后面的参数必须有缺省值
#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 ) ;
Func1 (d1);
cout << &d1 << endl;
Date d2 (&d1) ;
d1. Print ();
d2. Print ();
Date d3 (d1) ;
d2. Print ();
Date d4 = d1;
d2. Print ();
Date ret = Func2 ();
ret.Print ();
return 0 ;
}
5. 赋值运算符重载
5.1 运算符重载
类类型对象使用运算符时,必须调用对应的运算符重载
运算符重载是具有特殊名字的函数,它是由 operator 和运算符构成,它也有返回类型、参数列表和函数体
重载运算符的参数个数和该运算符作用的运算对象一样多
如果一个重载函数是成员函数,则它的第一个运算对象默认为隐式的 this 指针,因此参数比运算对象少一个
运算符重载后,其优先级和结合性不变
不能通过连接语法中没有的符号来创建新的操作符
( .* )( :: )( sizeof )( ?: )( . )五个括号内的运算符不能重载
重载运算符必须要有一个类类型参数
运算符重载要看它重载后是否有意义
重载++时,有前置++和后置++,无法很好区分,因此规定在后置++重载时,增加一个 int 的形参,与前置++构成重载
重载<<和>>时,要将其重载为全局变量,如果重载为成员函数,this 指针默认为第一个参数,即左操作数,不方便使用
注:重载为全局函数访问私有成员的解决方案:
成员放在公有区
为获取成员提供一个函数
友元函数
重载为成员函数
5.2 赋值运算符重载 赋值运算符重载是一个默认成员函数,用于两个已经存在的对象 的拷贝。区分:拷贝构造用于一个对象初始化给另一个要创建的对象
赋值运算符重载是⼀个运算符重载,规定必须重载为成员函数。赋值运算重载的参数建议写成 const 当前类类型引⽤,否则会传值传参会有拷⻉
有返回值,且建议写成当前类类型引⽤,引⽤返回可以提⾼效率,有返回值⽬的是为了⽀持连续赋值场景
没有显式实现时,编译器会⾃动⽣成⼀个默认赋值运算符重载,跟默认拷⻉构造类似,对内置类型成员变量会完成值拷⻉/浅拷⻉ (逐字节拷⻉),对⾃定义类型成员变量会调⽤他的赋值重载函数
如果⼀个类显⽰实现了析构并释放资源,那么他就需要显⽰写赋值运算符重载,否则就不需要
6. 取地址运算符重载
6.1 const 成员函数
将 const 修饰的成员函数称之为 const 成员函数,const 修饰成员函数放到成员函数参数列表的后⾯
const 实际修饰该成员函数隐含的 this 指针,表明在该成员函数中不能对类的任何成员进⾏修改
#include <iostream>
using namespace std;
class Date
{
public :
Date (int year = 1 , int month = 1 , int day = 1 )
{
_year = year;
_month = month;
_day = day;
}
void Print () const
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private :
int _year;
int _month;
int _day;
};
int main ()
{
Date d1 (2024 , 7 , 5 ) ;
const Date d2 (2024 , 8 , 5 ) ;
return 0 ;
}
6.2 取地址运算符重载 取地址运算符重载分为普通取地址运算符重载和 const 取地址运算符重载,⼀般这两个函数不需要自己去显⽰实现
7. 构造函数——初始化列表
构造函数初始化还有一种方式:初始化列表,它的使用方式是以一个冒号开头,接着是⼀个以逗号分隔的数据成员列表,每个成员变量后⾯跟⼀个放在括号中的初始值或表达式
每个成员变量在初始化列表中只能出现⼀次
引⽤成员变量 ,const 成员变量 ,没有默认构造的类类型变量 ,必须放在初始化列表进⾏初始化
C++11⽀持在成员变量声明的位置给缺省值,这个缺省值主要是给没有显⽰在初始化列表初始化的成员使⽤的
初始化尽量使用初始化列表
⽆论是否显⽰写初始化列表,每个构造函数都有初始化列表
⽆论是否在初始化列表显⽰初始化成员变量,每个成员变量都要⾛初始化列表初始化
初始化列表中按照成员变量在类中声明顺序 进⾏初始化,跟成员在初始化列表出现的的先后顺序⽆关
#include <iostream>
using namespace std;
class Time
{
public :
Time (int hour) :_hour(hour)
{
cout << "Time()" << endl;
}
private :
int _hour;
};
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;
};
int main ()
{
int i = 0 ;
Date d1 (i) ;
return 0 ;
}
五、类型转换
C++⽀持内置类型 隐式类型转换为类类型对象 ,需要有相关内置类型为参数的构造函数
相关构造函数前⾯加explicit 就不再⽀持隐式类型转换
类类型的对象之间 也可以隐式转换,仍然需要相应的构造函数⽀持
#include <iostream>
using namespace std;
class A
{
public :
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 ()
{
A aa1 = 1 ;
aa1. Print ();
const A& aa2 = 1 ;
A aa3 = { 1 ,1 };
B b = aa3;
const B& rb = aa3;
return 0 ;
}
六、static 成员
⽤ static 修饰的成员变量,称之为静态成员变量,静态成员变量⼀定要在类外进⾏初始化
静态成员变量为所有类对象所共享,不属于某个具体的对象,不存在对象中,存放在静态区
⽤ static 修饰的成员函数,称之为静态成员函数,没有 this 指针
静态成员函数中可以访问其他的静态成员,但是不能访问⾮静态的,因为没有 this 指针
⾮静态的成员函数,可以访问任意的静态成员变量和静态成员函数
突破类域就可以访问静态成员,可以通过类名::静态成员或者对象。静态成员来访问静态成员变量和静态成员函数
静态成员也是类的成员,受访问限定符的限制
静态成员变量不能在声明位置给缺省值初始化,因为缺省值是个构造函数初始化列表的,静态成员 变量不属于某个对象,不⾛构造函数初始化列表
#include <iostream>
using namespace std;
class A
{
private :
static int _scount;
};
int A::_scount = 0 ;
七、友元
友元提供了⼀种突破类访问限定符封装的⽅式,但不是很推荐使用
友元分为:友元函数和友元类,在函数声明或者类声明的前⾯加 friend,并且把友元声明放到⼀个类的⾥⾯
外部友元函数可访问类的私有和保护成员
友元函数可以在类定义的任何地⽅声明,不受类访问限定符限制
⼀个函数可以是多个类的友元函数
友元类中的成员函数都可以是另⼀个类的友元函数,都可以访问另⼀个类中的私有和保护成员
友元类的关系是单向的,不具有交换性,如 A 是 B 的友元,但是 B 不是 A 的友元
友元类关系不能传递,如果 A 是 B 的友元,B 是 C 的友元,但是 A 不是 C 的友元
#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 :
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;
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 ();
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;
f2 ();
cout << endl;
A aa2 = f2 ();
cout << endl;
aa1 = f2 ();
cout << endl;
return 0 ;
}
相关免费在线工具 Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
Markdown转HTML 将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
HTML转Markdown 将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
JSON 压缩 通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
JSON美化和格式化 将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online