举爪爪打招呼

很高兴你点开这篇文章✨

这里会持续更新我喜欢的内容,关注我,一起慢慢变好呀

👍 点赞 ⭐ 收藏 💬 评论


前言

如果你刚从C语言的结构体(struct)来到C++,可能会问:C++的类(class)到底是什么?它和struct有什么区别?为什么类里面既能放变量又能放函数?还有,那个神秘的this指针又是怎么回事?

💡 带着疑问,我们从一个简单的Stack类和Date类出发,一步步拆解C++类的核心概念。你会学到:

  • public和private到底在保护什么
  • 类的声明和定义为什么要分开写
  • 对象在内存里长什么样,占多少字节
  • this指针是编译器偷偷塞给你的秘密武器

一、类的定义格式

1.1 一个简单的Stack类

🐾 类定义的格式

class Stack
{
    // 类的方法/成员函数
    void Init();            // 开头没有访问限定符,默认为private

public:
    void Push(int x);
    int Top();
    void Destroy();			//public内的成员可直接访问,直到下一个访问修饰符的出现

private:
    // 类的属性/成员变量
    int* arr;
    int top;
    int capacity;
};				//到 } 结束

1.2 访问限定符

限定符 含义
public 公开的,类内外都可以访问
private 私有的,只能在类内部访问
protected 受保护的,类内部和派生类可访问(继承时用到)

🐾 规则:

  • 类中如果没有写访问限定符,默认是private
  • 访问限定符的作用范围从它出现开始,到下一个限定符或类结束为止
int main()
{
    Stack st;
    st.Push(1);     // ✅ public,可以访问
    st.Init();      // ❌ private,无法访问
    st.arr;         // ❌ private,无法访问
    return 0;
}

二、struct vs class

C++兼容C语言的struct,同时引入了class。

对比项 struct class
默认访问权限 public private
使用场景 通常用于简单的数据聚合 通常用于完整的类设计
// C++中的struct(默认public)
struct Date
{
    void Init(int year, int mouth, int day)
    {
        _year = year;
        _mouth = mouth;
        _day = day;
    }
private:
    int _year;
    int _mouth;
    int _day;
};

// 链表节点的写法对比
typedef struct ListNodeC      // C风格
{
    int val;
    struct ListNodeC* next;
} LTNodeC;

struct ListNodeCPP            // C++风格
{
    int val;
    ListNodeCPP* next;        // 不需要重复写struct
};

建议:用class定义类,用struct定义简单的数据集合。


三、成员变量的命名规范

为了避免成员变量和函数参数冲突,C++中常见的命名习惯:

class Date
{
public:
    void Init(int year, int mouth, int day)
    {
        _year = year;      // 成员变量加下划线前缀
        _mouth = mouth;
        _day = day;
    }
private:
    int _year;             // 方式1:_year
    int mouth_;            // 方式2:mouth_
    int m_day;             // 方式3:m_day
};

🐾 常见风格:

  • 1、 _year(前缀下划线)
  • 2、 year_(后缀下划线)
  • 3、 m_year(m前缀)

选择一种,保持一致即可。


四、类域与声明定义分离

4.1 什么是类域?

类中的成员(变量和函数)属于类域。编译器查找一个名字时,会按照:局部域 -> 全局域 -> 类域的顺序查找。


4.2 声明和定义分离

成员函数可以在类内声明,在类外定义。类外定义时需要指定类域。

class Stack
{
public:
    void Init(int n = 4);   // 声明
private:
    int* arr;
    int top;
    int capacity;
};

// 类外定义,必须加 Stack:: 指定类域
void Stack::Init(int n)
{
    arr = (int*)malloc(sizeof(int) * n);
    if (arr == nullptr)
    {
        perror("malloc申请空间失败");
        return;
    }
    capacity = n;
    top = 0;
}

如果不指定类域会怎样?

void Init(int n)   // ❌ 编译器把Init当成全局函数,找不到arr等成员
{
    arr = (int*)malloc(...);   // 报错:arr未定义
}

五、实例化:从类到对象

  • 类: 是一种抽象描述,定义了有哪些成员变量和成员函数。成员变量只是声明,不分配空间。

  • 对象: 用类类型在物理内存中创建的具体实体。实例化对象时,才会给成员变量分配空间。

class Date
{
public:
	//初始化函数,给日期类的成员变量赋值
    void Init(int year, int mouth, int day)
    {
    //给类的私有成员(private)_year/_mouth/_day赋值
        _year = year;
        _mouth = mouth;
        _day = day;
    }
private:
    int _year;   // 声明,还未分配空间
    int _mouth;
    int _day;
};

int main()
{
    Date d1;     // 实例化对象d1,此时才分配空间
    Date d2;     // 实例化对象d2,分配独立的空间
    return 0;
}

类比:类 = 建筑设计图,对象 = 根据图纸盖出来的房子。


六、对象的大小计算

6.1 成员函数不占对象空间

对象中只存储成员变量,成员函数存储在代码段,所有对象共享。

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;   // 8(char对齐后占4 + int占4)
    cout << sizeof(b) << endl;   // 1(空类占1字节,用于标识对象地址)
    cout << sizeof(c) << endl;   // 1(空类占1字节)
}

6.2 对象大小计算规则

规则 说明
只计算成员变量 成员函数不计入
内存对齐 和C语言结构体对齐规则一致
空类占1字节 用于区分不同的空对象

七、this指针

7.1 this指针的本质

在C++中,成员函数被编译时,编译器会隐式地添加一个参数:指向当前对象的指针this。

  • 类的成员函数中访问成员变量,本质都是通过 this 指针访问的
class Date
{
public:
    void Init(int year, int mouth, int day)
    {
        _year = year;
        _mouth = mouth;
        _day = day;
    }
};

// 编译器实际处理的形式
class Date
{
public:
    void Init(Date* const this, int year, int mouth, int day)
    {
        this->_year = year;
        this->_mouth = mouth;
        this->_day = day;
    }
};

// 调用时
Date d1;
d1.Init(2026, 3, 22);
// 编译器转换为:d1.Init(&d1, 2026, 3, 22);

7.2 this指针的特性

特性 说明
类型 ClassName* const this(指针本身不可改,指向的内容可改)
传递方式 编译器自动传递,不需要程序员手动传
存储位置 通常是寄存器(如ECX)或栈
是否可以为空 不访问成员变量时,this可以是nullptr

7.3 经典面试题:空指针调用成员函数

class A
{
public:
    void Print()
    {
        cout << "A::Print()" << endl;   // 没有访问成员变量
    }
    void PrintA()
    {
        cout << _a << endl;             // 访问了成员变量
    }
private:
    int _a = 1;
};

int main()
{
    A* p = nullptr;
    p->Print();    // ✅ 可以运行!因为没有解引用this
    // p->PrintA(); // ❌ 崩溃!因为this是nullptr,访问_a相当于this->_a
    return 0;
}

结论:空指针可以调用不访问成员变量的成员函数,但不能调用访问成员变量的成员函数。


八、知识点汇总

知识点 核心要点
访问限定符 public(公开)、private(私有)、protected(保护)
默认权限 class默认private,struct默认public
类域 查找顺序:局部 → 全局 → 类域
声明定义分离 类外定义需加类名::指定类域
实例化 类不占空间,对象实例化时分配空间
对象大小 只算成员变量,遵循内存对齐,空类占1字节
this指针 编译器隐式传递的指向当前对象的指针
空指针调用 不访问成员变量时可以,访问则会崩溃

九、总结

  • 访问限定符实现了封装,让代码更安全
  • 类域影响了编译器查找名字的规则
  • 实例化是从模型到实体的过程
  • 对象大小只包含成员变量,不包含成员函数
  • this指针是编译器在幕后帮我们干的活

十、本文所有的代码

#define _crt_secure_no_warnings 1
#include<string>
#include<iostream>
using namespace std;

////类定义的格式
class stack
{

	//类的方法/成员函数
	void init();			//开头没有访问限定符修饰,则默认为private

public:
	void push(int x);
	int top();
	void destroy();	//public内的成员可直接访问,直到下一个访问修饰符的出现,也就是下面的private

private:
	//类的属性/成员变量
	int* arr;
	int top;
	int capacity;
	//后面没有访问限定符出现时,到 } 结束
};

int main()
{
	stack st;
	st.push(1);
	st.top();
	st.init();		//err:无法访问 private 成员
	st.arr;			//err:无法访问 private 成员
	return 0;
}

//////////////////////////////////////////////////////////////////////////////////////
//
class date
{
public:
	void init(int year, int mouth, int day)
		//下划线_+名称(_year,_mouth,_day)是为了怕起冲突所加的特殊标记,
		//避免输入year时系统不知道用publi的还是private的
	{
		_year = year;
		_mouth = mouth;
		_day = day;
	}
private:
	//为了区分成员变量,一般习惯上在成员变量上加一个特殊标记,如:在前面或后面加 _ 或 m开头
	int _year;			//_year,year_,myear
	int _mouth;
	int _day;
};
int main()
{
	date date;
	date.init(2026, 3, 22);
	return 0;
}


struct date
{
	void init(int year, int mouth, int day)
	{
		_year = year;
		_mouth = mouth;
		_day = day;

	}
private:
	int _year;
	int _mouth;
	int _day;

};

//c++兼容c中struct的用法
// struct 定义类时,没有被访问限定符修饰时,默认是private
typedef struct listnodec
{
	int val;
	struct listnodec* next;
}ltnodec;

struct listnodecpp
{
	int val;
	listnodecpp* next;
};

int main()
{
	date date;
	date.init(2026,3,22);
	return 0;
}

 
 //stack(类域)
 //类域影响的是编译的查找规则,下面程序中init
 //1.如果不指定类域stack,那么编译器就会把init当成全局函数,编译时找不到arr等成员的声明/定义在哪里,就会报错
 //2.指定类域stack,就是知道init是成员函数,当前域找不到arr等成员,就会到类域中去找
class stack
{
public:
 //成员函数
	void init(int n = 4);
private:
 //成员变量
	int* arr;
	int top;
	int capacity;
};

////////////////////////////////////////////////////////////////////////////////////////

 //声明和定义分离,需要指定类域
//void init();//err:由于没指定类域stack,则当成全局函数,
 //但是在全局中和局部中均没有arr等成员的声明和定义,则会报错
 
void stack::init(int n)//此时指定了类域后,除了全局和部局,编译器还会在指定类域中查找成员
{
	arr = (int*)malloc(sizeof(int) * n);
	if (arr == nullptr)
	{
		perror("malloc申请空间失败咯!");
		return;
	}
	capacity = n;
	top = 0;
}

int main()
{
	stack st;
	st.init();
	return 0;
}

 
 ////实例化:用类类型在物理内存中创建对象的过程
 ////类是对象进行一种抽象描述,是一个模型一样的东西,限定了类有哪些成员变量,
 ////这些成员变量只是声明,没有分配空间,用类实例化出对象时,才会分配空间
class date 
{
public:

				//初始化函数,给日期类的成员变量赋值
	void init(int year, int mouth, int day)
	{
				//给类的私有成员(private)_year/_mouth/_day赋值
		_year = year;
		_mouth = mouth;
		_day = day;
	}
	void print()
	{
		cout << _year << "/" << _mouth << "/" << _day;
	}
private:
 //加了static就变成了静态成员变量:属于类本身,所有对象公用一份_year
	static int _year;
 //普通成员变量:属于每个对象,每个对象都有自己独立的_mouth,_day
	int _mouth;
	int _day;
};
 
 
 //静态成员变量不能在类内初始化,必须在类外、全局作用域初始化
 //格式为 类型 类名::静态成员名 = 初始值; 
int date::_year = 0;
 
int main()
{
 
 //date类实例化出对象d1和d2
	date d1;
	date d2;
	d1.init(2026, 3, 22);
	d1.print();
	d2.init(2026, 3, 22);
	d2.print();
	return 0;
}

/////计算一下a/b/c实例化的对象多大
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指针
 ////类的成员函数中访问成员变量,本质都是通过 this 指针访问的
 ////如 init 函数中给 _year 赋值:this->_year = year;
class date
{
public:
	void init(int year, int mouth, int day)
				//void init(date* const this,int year,int mouth,int day)
	{
		_year = year;
		_mouth = mouth;
		_day = day;
				//this->_year = year;
				//this->mouth = mouth;
				//this->_day = day;
	}

	void print()	//这是我们写代码时的简洁写法
			//void print(date* const this):这是编译器底层处理的形式
			//this是指向当前调用该函数的date对象的指针
	{
		cout << _year << "/" << _mouth<<"/" << _day << endl;
			//cout<<this->_year<<"/"<<this->_mouth<<"/"<<this->_day<<endl;
	}

private:
	static int _year;
	int _mouth;
	int _day;
};

int main()
{
		//date类实例化出对象d1和d2
	date d1;
	date d2;
	d1.init(2026, 3, 22);
		//d1.init(&d1,2026,3,22);
	d1.print();
		//d1.print(&d1);
	d2.init(2026, 3, 22);
		//d2.init(&d2,2026,3,22)
	d2.print();
		//d2.print(&d2);
	return 0;
}

 
 
class a
{
public:
	void print()
	{
		cout << "a::print()" << endl;
		//cout<<_a<<endl;
	}
private:
	int _a = 1;
};

int main()
{
	a* p = nullptr;
	p->print();

	//p->_a=1;

	return 0;
}

🐾 下一篇我们继续学习:

  • Date类的设计与实现
  • 运算符重载
  • 拷贝构造函数
  • 流插入和流提取(cout、cin)

举爪爪求关注

谢谢你看到这里呀

如果喜欢这篇内容,点个关注,下次更新不迷路✨

👍 点赞 ⭐ 收藏 💬 评论

更多推荐