C++初阶2:类和对象 - 详解
目录

一、类的定义
class Month
{
int a;
char c;
public:
void Printf()
{
cout << a << endl;
}
};
int main()
{
Month a;
a.Printf();
return 0;
}
其中我们用到的public是访问限定符,除了public之外还有为private和protected,这是C++一种类似访问权限的方法,通过访问权限选择性的将其接口提供给外部的用户使用。
在现阶段我们只能知道的,public修饰的成员在类外可以直接被访问;protected和private修饰的成员在类外不能直接被访问,protected和private是⼀样的,以后学习才能体现出他们的区别。
class 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 d;
d.Init(2024, 3, 31);
return 0;
}
class Date
{
public:
void Init(int year, int month, int day);
private:
// 为了区分成员变量,一般习惯上成员变量前加上 _
int _year;
int _month;
int _day;
};
//没显示函数是Data域
void Init(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
//有显示
void Date::Init(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
int main()
{
Date d;
d.Init(2024, 3, 31);
return 0;
}
二、实例化
class Date
{
public:
void Init(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类实例化出对象d1和d2
Date d1;
Date d2;
d1.Init(2025, 11, 18);
d1.Print();
d2.Init(2025, 11, 19);
d2.Print();
return 0;
}
1. 基本数据类型的对齐值,每个基本类型有默认的自身对齐值(通常等于其字节大小)
2. 结构体的内存对齐规则,结构体的对齐需满足3条核心规则:
1. 成员对齐:结构体每个成员的起始地址,必须是其“自身对齐值”的整数倍;
2. 整体对齐:结构体的总大小,必须是其“最大成员对齐值”的整数倍;
3. 编译器对齐单位:若编译器指定了对齐单位(如#pragma pack(n)),则对齐值取“自身对齐值”与n的较小值。
如果实例化对象没有成员变量也会占一个字节,因为如果一个字节都不给,怎么表示对象存在过呢!
class A
{
public:
void Print()
{
cout << _ch << endl;
}
private:
char _ch;
int _i;
};
class B
{
public:
void Print()
{
//...
}
};
class C
{};
int main()
{
A a;
B b;
C c;
cout << sizeof(a) << endl;
cout << sizeof(b) << endl;
cout << sizeof(c) << endl;
return 0;
}
三、this指针
我们在一个类中调用函数时,函数体中没有关于不同对象的区分,那该如何知道函数访问的是那个对象呢。C++有一个隐含的this指针解决这里的问题。编译器编译后,类的成员函数默认都会在形参第一个位置,增加一个当前类类型的指针,叫做this指针。所以我们看下面的例子,我们写这个函数是这样的,但本质上它是那样的。
但是不能在实参和形参的位置显示的写this指针(编译时编译器会处理),但是可以在函数体内显示使用this指针。
// void Init(Date* const this, int year, int month, int day)
/*void Init(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}*/
//可以这样写
void Init(int year, int month, int day)
{
this->_year = year;
this->_month = month;
this->_day = day;
}
四、类的默认成员函数
类是默认成员函数是指我们没有主动显示实现但编译器会自己主动生成的成员函数。一个类,我 们不写的情况下编译器会默认生成成以下6个默认成员函数,这里我们主要了解前四个。有人可能会疑惑为什么我们要主动了解这些函数,这难道不是编译器会帮我们解决的吗?我们是要搞明白编译器生成了什么样的成员函数帮我们解决了问题,如果有编译器生成的函数无法解决的问题我们又该如何自己去实现。

五、构造函数
构造函数是特别的成员函数,但我们要注意构造函数的本质并不是去开辟空间。虽然它可以做到,但它的本质是一个类似于初始化的过程。就像下面我们的类Data中实例化出对象但没有开辟空间。但是对于Stack它就需要开辟空间。所以说我们可以把它理解为以前写Satck时Init的作用。
#include
using namespace std;
class Data
{
public:
//无参
Data( )
{
_year = 1;
_month = 1;
_day = 1;
}
//带参
Data (int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
//全缺省
Data(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
void Print( )
{
cout << _year << '/' << _month << '/' << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Data d1;
Data d2(2025, 11, 19);
Data d3();
d1.Print();
d2.Print();
d3.Print();
}
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;
}
private:
STDataType* _a;
size_t _capacity;
size_t _top;
};
class Data
{
public:
//无参
Data( )
{
_year = 1;
_month = 1;
_day = 1;
}
//带参
Data (int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
//全缺省
Data(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
void Print( )
{
cout << _year << '/' << _month << '/' << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
我们要注意如果使用无参构造函数时不要在对象后面跟括号,否则编译器不知道你是想实例化对象还是函数声明。
int main()
{
Data d1;
Data d2(2025, 11, 19);
Data d3();
d1.Print();
d2.Print();
//d3.Print();
编译器默认生成MyQueue的构造函数调用了Stack的构造,完成了初始化
六、析构函数
析构函数与构造函数功能相反,析构函数也不是销毁的意思,因为局部对象是存在栈帧的,函数结束的时候就自己释放了所以不用我们操心。析构函数更像是避免资源浪费,像我们在Stack里的的Destroy功能,但是我们之前写的Data就不需要析构函数,因为它没有资源需要释放,就差不多是没有申请过空间,不需要把指针置空等等。
析构函数的特点:
注意:一个局部域的多个对象,C++规定后定义的先析构。
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;
};
int main()
{
Stack st;
MyQueue mq;
return 0;
}
七、拷贝构造函数
拷贝函数是一个特殊的构造函数。因为C++规定在传值传参时要调用拷贝构造,所以我们实现拷贝构造时要传引用传值。如果直接传值传参会形成新的拷贝构造会无限递归下去。如下

class Data
{
public:
void Print()
{
cout << _year << '/' << _month << '/' << _day << endl;
}
Data(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
Data(Data& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Data d1(2025, 11, 20);
Data d2(d1);
d2.Print();
}
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(Stack& st)
{
_a = (STDataType*)malloc(sizeof(STDataType) * st._capacity);
if (nullptr == _a)
{
perror("malloc申请空间失败!!!");
return;
}
memcpy(_a, st._a, sizeof(STDataType) * st._top);
_top = st._top;
_capacity = st._capacity;
}
void Push(STDataType x)
{
if (_top == _capacity)
{
int newcapacity = _capacity * 2;
STDataType* tmp = (STDataType*)realloc(_a, newcapacity *
sizeof(STDataType));
if (tmp == NULL)
{
perror("realloc fail");
return;
}
_a = tmp;
_capacity = newcapacity;
}
_a[_top++] = x;
}
~Stack()
{
cout << "~Stack()" << endl;
free(_a);
_a = nullptr;
_top = _capacity = 0;
}
private:
STDataType* _a;
size_t _capacity;
size_t _top;
};
int main()
{
Stack st1;
st1.Push(1);
st1.Push(2);
//和Stack st2(st1)一样
Stack st2 = st1;
//Data那个也可以写成Data d2 = d1
return 0;
}
补充:
有人可能对这几种传值的方式不太理解,这里给大家总结一下
• 传值返回:返回变量的副本,函数结束后原局部变量销毁,副本独立存在,开销=变量大小(适合基础类型)。
• 指针返回:返回变量的内存地址,需确保指向的变量(如全局/静态变量、堆内存)生命周期长于函数,避免返回局部变量指针(悬垂指针)。
• 引用返回:返回变量的别名(无副本开销),本质是指针优化,必须指向有效变量(不能返回局部变量引用),可直接修改原变量。

// 传值返回
int add(int a, int b) {
int res = a + b;
return res; // 返回res的副本
}
// 指针返回(静态变量,生命周期长)
int* getStaticNum() {
static int num = 10;
return # // 安全,静态变量在程序结束前不销毁
}
// 引用返回(返回全局变量别名)
int g_num = 20;
int& getGlobalNum() {
return g_num; // 安全,全局变量生命周期贯穿程序
}
八、赋值运算符重载
#include
using namespace std;
class Data
{
public:
void Print()
{
cout << _year << '/' << _month << '/' << _day << endl;
}
Data(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
Data operator+(const Data& d2)
{
return Data(_year + d2._year,
_month + d2._month,
_day + d2._day);
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Data d1(2025,11,22);
d1.Print();
Data d2(0, 0, 2);
Data d3 = d1 + d2;
d3.Print();
Data d4 = d1.operator+(d3);
d4.Print();
return 0;
}
#include
using namespace std;
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
Date& operator++()
{
cout << "前置" << endl;
_day++;
return *this;
}
Date& operator++(int)
{
cout << "后置" << endl;
Date tmp = *this;
_day++;
return tmp;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2025,11,22);
d1.Print();
Date d2 = d1++;
d2.Print();
d1.Print();
Date d3 = ++d1;
d3.Print();
d1.Print();
return 0;
}
ostream& operator<<(ostream& out, const Date& d)
{
out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
return out;
}
#include
using namespace std;
class Date
{
public:
//构造
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
//赋值
Date& operator=(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
return *this;
}
// 拷贝构造
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
// 普通自定义构造
Date(const 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;
};
int main()
{
//构造
Date d1(2025, 11, 23);
d1.Print();
// 拷贝构造
Date d2(d1);
d2.Print();
// 普通自定义构造
Date d3(&d1);
d3.Print();
//赋值
Date d4 = d1;
d4.Print();
return 0;
}
我们注意到对于赋值运算符重载的返回值我们一般都是引用返回,这样做也是为了避免发生拷贝,也为了支持连续赋值场景。
而且赋值运算符重载也和前面提到过的Stack的情况一样,像Date这样的类成员变量全是内置类型且没有指向什么资源,编译器自动生成的赋值运算符重载就可以完成需要的拷贝,所以不需要我们显示实现赋值运算符重载。像Stack这样的类,虽然也都是内置类型,但是_a指向了资源,编译器自动生成的赋值运算符重载完成的值拷贝/浅拷贝不符合我们的需求,所以需要我们自己实现深拷贝(对指向的资源也进行拷贝)。像MyQueue这样的类型内部主要是自定义类型Stack成员,编译器自动生成的赋值运算符重载会调用Stack的赋值运算符重载,也不需要我们显示实现MyQueue的赋值运算符重载。
我们学习到这个地方后就可以解决日期类的计算问题,大家可以自己尝试一下,我的源代码链接在这里,关于这里还有一些细节,下面我也会为大家解答。https://gitee.com/chen-chen-chen-chen-ch/c-.cpp/commit/44ec2dd5a5697f6b7c34e8192f72f1cd0ffacd49
九、取地址运算符重载
如果大家看到了我的日期类计算代码就会看到我在很多成员函数的后面都加了const,这里的const其实是在修饰成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。比如void Print()函数的隐含的this指针就由Date* const this 变成了const Date* const this.
class Date
{
public:
Date * operator&()
{
return this;
return nullptr;
}
const Date * operator&()const
{
return this;
// return nullptr;
}
private:
int _year;
int _month;
int _day;
};
十、初始化列表
初始化列表实际上也是一种构造函数的方法,我们使用构造函数初始化成员变量是在函数体内进行赋值,初始化列表是以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括号中的初始值或表达式。(初始化列表就是在构造函数的函数体外)无论是否显示写初始化列表,每个构造函数都有初始化列表。每个成员变量只能使用一次。
尽量使用初始化列表初始化,因为那些你不在初始化列表初始化的成员也会走初始化列表,如果这 个成员在声明位置给了缺省值,初始化列表会用这个缺省值初始化。如果你没有给缺省值,对于没 有显示在初始化列表初始化的内置类型成员是否初始化取决于编译器,C++并没有规定。对于没有 显示在初始化列表初始化的自定义类型成员会调用这个成员类型的默认构造函数,如果没有默认构 造会编译错误。
还有三个特殊的变量必须放在初始化列表位置进行初始化,否则会编译报错。
#include
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)
{
}
void Print() const
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
Time _t;
int& _ref;
const int _n;
};
int main()
{
int i = 0;
Date d1(i);
d1.Print();
return 0;
}
C++11支持在成员变量声明的位置给缺省值,这个缺省值主要是给没有显示在初始化列表初始化的 成员使用的。(注意这里的缺省值和我们在构造函数中说的那个不能同等理解,这里的缺省值是只有初始化列表没有初始化的时候才会使用)

#include
using namespace std;
class Time
{
public:
Time(int hour)
:_hour(hour)
{
cout << "Time()" << endl;
}
private:
int _hour;
};
class Date
{
public:
Date()
:_month(2)
{
cout << "Date()" << endl;
}
void Print() const
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year = 1;
int _month = 1;
int _day;
Time _t = 1;
const int _n = 1;
int* _ptr = (int*)malloc(12);
};
int main()
{
Date d1;
d1.Print();
return 0;
}
一般按声明顺序初始化。
十一、类型转换
#include
using namespace std;
//转换
class A
{
public:
A(int a)
{
_a = a;
}
void Print()
{
cout << _a << endl;
}
private:
int _a;
};
int main()
{
A aa = 1;
aa.Print();
return 0;
}
//不能转换
class A
{
public:
explicit A(int a)
{
_a = a;
}
void Print()
{
cout << _a << endl;
}
private:
int _a;
};
int main()
{
A aa = 1;
aa.Print();
return 0;
}
十二、static成员
class A
{
public:
A()
{
++_scount;
}
A(const A& t)
{
++_scount;
}
~A()
{
--_scount;
}
static int GetACount()
{
return _scount;
}
private:
static int _scount;
};
int A::_scount = 0;
int main()
{
cout << A::GetACount() << endl;
A a1, a2;
A a3(a1);
cout << A::GetACount() << endl;
cout << a1.GetACount() << endl;
return 0;
}
十三、友元
#include
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
using namespace std;
class A
{
private:
static int _k;
int _h = 1;
public:
class B
{
public:
void foo(const A& a)
{
cout << _k << endl; //OK
cout << a._h << endl; //OK
}
int _b1;
};
};
int A::_k = 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)
{
cout << "A(int a)" << endl;
}
~A()
{
cout << "~A()" << endl;
}
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;
}
十六、对象拷贝时的编译器优化
class A
{
public:
//构造
A(int a1 = 0, int a2 = 0)
:_a1(a1)
, _a2(a2)
{
cout << "A(int a1 = 0, int a2 = 0)" << 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()
{
//delete _ptr;
cout << "~A()" << endl;
}
void Print()
{
cout << "A::Print->" << _a1 << endl;
}
A& operator++()
{
_a1 += 100;
return *this;
}
private:
int _a1 = 1;
int _a2 = 1;
};
//int main()
//{
// A aa1 = 1;
// const A& aa2 = 1;
//
// return 0;
//}
浙公网安备 33010602011771号