类的定义格式

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
五、一句话终极总结
  1. 成员函数代码全局唯一,存在代码区,所有对象共用
  2. 所以所有对象的成员函数指针完全相同
  3. 靠 this 指针区分当前操作的是哪个对象的变量

类实例化出的每个对象都有独立的数据空间,对象中包含成员变量

而成员函数:函数被编译后是一段指令,对象中没法存储。这些指令存储在一个单独区域(代码段)。对象中只能存储函数的指针,但每个对象调用的是同一个函数,所以只用存储成员变量

若没有成员变量的对象大小为1(是为了占位,标识对象存在。比如仿函数)

this指针

类中的成员函数本质就是通过this访问属性

void stack::Init(int x)
{
    this->_x = x;
}

不能在实参形参的位置显示写this指针,但可以在函数体显示使用

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐