C++类与对象:从定义到内存模型全解析
类的定义格式
class stack
{
public:
void push()
{
//...
}
private:
int x;
};
class为定义类的关键字,stack为类的名字,{}中为类的主体(注:类定义结束后面有分号)
类的成员:类中的变量称为类的属性(成员变量)
类中的函数称为类的方法(成员函数)
访问限定符
C++一种实现封装的方式,用类将对象的属性与方法结合在一块,让对象更加完善。通过访问限定符选择性的将其接口提供给外部使用
public修饰的成员在类外可以直接被访问(protected和private修饰的不能)
访问权限作用域从该访问的限定符出现的位置开始,直到下一个访问限定符出现时为止
class若无访问限定符,则默认为private;struct默认为public
一般属性会被限制(不能在外部被任意修改),方法为public(需要给别人用)
public:
void Init(int x)
{
_x = x;
}
private:
int _x;
问:private: int _x; 在干什么
在这里并没有开空间,而是声明一个成员变量。
可以把它理解成:给这个类 “预定” 了一个叫 _x 的整数位置,但现在还没放东西,也没真正分配内存
错误:在方法中int _x=x;
void Init(int x)
{
int _x = x;
}
_x并非局部变量,而是在stack类的private区域已经声明过的属性
在private中int _x为声明;Init函数中的_x=x为赋值。若再次int _x=x则是在Init中创建一个局部变量(覆盖类的属性)
C++中,类的名称可以直接表示这个类型(不用像C语言那样typedef)
struct ListNode
{
int val;
ListNode* next;
};
若想声明定义分离,则把声明写在类内,而定义在类外。且定义时要指明类域
class stack
{
public:
void Init(int x);
private:
int _x;
};
void stack::Init(int x)
{
_x = x;
}
问:这里不是在类外访问类的私有成员了吗,为什么可以
关键就在于 stack::Init 这个写法:
stack::这个作用域解析符,明确告诉编译器:这个Init函数,是stack类的成员函数。- 类的所有成员函数,天生就有权限访问该类的私有成员变量和私有函数。
- 哪怕函数的实现写在类的外面,只要它是类的成员函数,就完全不影响访问权限。
不分离时默认是内联
内联
C语言实现的宏函数也会在预处理时替换展开,但宏函数实现复杂且易出错,内联函数inline目的就是替换C语言的宏
#define在预处理阶段做纯文本替换
inline函数是在编译阶段,为编译优化行为:由编译器决定是否展开。就算你写了inline,但对于编译器而言只是一个建议,也可能不会展开
inline展开:没有函数调用的开销,不创建新的栈桢,函数体的指令(汇编中可看)直接被插入到调用点中
普函数调用:会创建栈桢(保存返回地址,局部变量等),执行完再根据之前保存的地址返回
inline不建议声明和定义分离到两个文件(如声明在.h,定义在.c),会导致链接错误
inline适用于频繁调用的短小函数
类域
类定义了一个新的作用域
在类外定义成员,需要用类名::来指定类域
类域影响的是编译的查找规则
作用域的划分
什么是作用域
作用域 = 一个名字(变量 / 函数 / 类)能被 “看见” 的代码区域。不同的作用域有不同的 “可见范围”,出了这个范围,名字就失效了,编译器就找不到它了
1. 局部作用域(块作用域)
- 定义:用
{}包裹的代码块(函数体、if/for/while、自定义块) - 规则:
- 变量只在当前
{}内有效 - 出了
{}就直接销毁,外部完全访问不到
- 变量只在当前
void func()
{
int a = 10; // 作用域:整个func函数体
if (true)
{
int b = 20; // 作用域:只在这个if块里
cout << a << endl; // 可以访问外层的a
}
cout << b << endl; // 报错!b的作用域已经结束了
}
2. 函数作用域
- 定义:整个函数体(注意:函数本身的名字,作用域是全局 / 类;这里说的是函数内的标签、变量)
- 特殊点:
- 函数内的
goto标签,整个函数都能访问 - 函数参数,作用域就是整个函数体
- 函数内的
void func(int x) // x的作用域:整个func函数
{
// label 是函数作用域
goto end;
int a = 10;
end:
cout << "ok" << endl;
}
3. 类作用域
- 定义:类
class / struct内部的所有成员,共享的作用域 - 规则:
- 类内的成员函数,可以直接访问类的所有成员(public/private/protected)
- 类外要访问,必须用
对象.成员/对象指针->成员/类名::静态成员(静态成员可以不创建对象,只指明类域访问)
class stack
{
public:
void Init(int x); // 成员函数声明,作用域在stack类内
private:
int _x; // 私有成员,作用域在stack类内
};
// 类外定义,必须加 stack:: 说明是类作用域里的函数
void stack::Init(int x)
{
_x = x; // 成员函数,属于类作用域,能访问类内的_x
}
int main()
{
stack s;
cout << s._x; // 报错!_x是private,类外不能直接访问
}
4. 命名空间作用域
- 定义:用
namespace包裹的区域,用来避免名字冲突 - 规则:
- 命名空间内的名字,要访问必须加
命名空间::名字 - 可以用
using namespace 命名空间;把名字引入当前作用域
- 命名空间内的名字,要访问必须加
namespace myspace
{
int a = 100;
void func() { cout << "namespace func" << endl; }
}
int main()
{
cout << myspace::a << endl; // 正确访问
myspace::func(); // 正确调用
using namespace myspace;
cout << a << endl; // 引入后可以直接用
}
5. 全局作用域
- 定义:所有函数、类、命名空间之外的区域,整个文件都能访问
- 规则:
- 全局变量 / 函数,作用域是整个程序(跨文件要声明
extern) - 局部作用域的名字,会隐藏全局作用域的同名名字
- 全局变量 / 函数,作用域是整个程序(跨文件要声明
int a = 100; // 全局作用域的a
void func()
{
int a = 200; // 局部作用域的a,隐藏了全局的a
cout << a << endl; // 输出200(局部的a)
cout << ::a << endl; // 输出100(用::强制访问全局的a)
}
实例化
类是对象进行一种的抽象描述。限定了类有哪些成员,这些成员只是声明,没有分配空间。类实例化出对象时才分配空间
一个类可以实例化出多个对象,他们有各自独立的成员,但函数指针是一样的
问:为什么函数指针是一样的
同一个类实例化出来的所有对象,成员函数的地址(函数指针)完全一样,因为成员函数的代码只有一份,存在代码区,所有对象共用这一份代码
核心原理:成员变量 vs 成员函数,存储位置完全不同
1. 成员变量(每个对象独立)
- 存在对象自身的内存里
- 每 new / 定义一个对象,就新开一份成员变量空间
- 所以:对象 1 的_x ≠ 对象 2 的_x
2. 成员函数(所有对象共享)
- 成员函数的二进制代码,存在代码段(.text 段)
- 整个程序运行期间只存一份
- 不管你创建 1 个、100 个、10000 个对象,函数代码永远只有一份
- 所以:所有对象的成员函数指针 = 同一个地址
二、为什么函数能区分是哪个对象在调用?
你肯定会问:函数代码只有一份,那它怎么知道现在操作的是对象 A 的_x,还是对象 B 的_x?
秘密在于:this 指针
编译器会偷偷给每个非静态成员函数加一个参数:
// 你写的代码
void stack::Init(int x)
{
_x = x;
}
// 编译器真正编译成的样子
void stack::Init(stack* this, int x)
{
this->_x = x;
}
调用时:
s1.Init(10);
// 编译器偷偷转成:
Init(&s1, 10);
s2.Init(20);
// 编译器偷偷转成:
Init(&s2, 20);
结论:
- 函数代码只有一份 → 函数指针相同
- this 指针不同 → 知道操作哪个对象的成员变量
三、内存模型一眼看懂
【代码区:所有对象共享】
stack::Init 函数的二进制代码(只有一份!)
【栈/堆区:每个对象独立】
对象 s1: [ _x ] → 属于s1的变量
对象 s2: [ _x ] → 属于s2的变量
- 调用
s1.Init()→ 跳去同一份 Init 代码,带&s1 - 调用
s2.Init()→ 跳去同一份 Init 代码,带&s2
五、一句话终极总结
- 成员函数代码全局唯一,存在代码区,所有对象共用
- 所以所有对象的成员函数指针完全相同
- 靠 this 指针区分当前操作的是哪个对象的变量
类实例化出的每个对象都有独立的数据空间,对象中包含成员变量
而成员函数:函数被编译后是一段指令,对象中没法存储。这些指令存储在一个单独区域(代码段)。对象中只能存储函数的指针,但每个对象调用的是同一个函数,所以只用存储成员变量
若没有成员变量的对象大小为1(是为了占位,标识对象存在。比如仿函数)
this指针
类中的成员函数本质就是通过this访问属性
void stack::Init(int x)
{
this->_x = x;
}
不能在实参形参的位置显示写this指针,但可以在函数体显示使用
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)