引言: 为了改善C语言struct的一些不足,C++引入了类。前置知识:C语言结构体

C语言写一个栈的相关内容大致如下

#include<stdio.h>
#include<stdlib.h>
typedef int T;
struct Stack
{
    T* arr;
    int top;        // 栈顶
    int capacity;   // 总容量
};

void Init(Stack* st){
    st->arr = (T*)malloc(sizeof(T) * 100);
    st->top = 0, st->capacity = 100;
}
T Top(Stack* st)
{
	return st->arr[st->top - 1];
}
void Push(Stack* st, T x){
    // 空间满了扩容
    if(st->top == st->capacity)
    {
        st->arr = (T*)realloc(st->arr, sizeof(T) * st->capacity * 2);
        st->capacity *= 2;
    }
    st->arr[st->top++] = x;
}
void Pop(Stack* st){
    st->top--;
}
bool Empty(Stack* st){
    return st->top == 0;
}

int main()
{
    Stack st;
    Init(&st);   
    Push(&st, 1);
    // ...
    return 0;
}

C++封装封装

C++对结构体进行升级,可以将相关函数放到结构体里面。也就是封装起来

  • 结构体里面的变量称为成员变量
  • 结构体里面的函数称为成员函数(函数也可以像变量那样通过.调用)
#include<stdio.h>
#include<stdlib.h>
typedef int T;
struct Stack
{
// Init、Push、Pop、Empty都为成员函数
    void Init(Stack* st){
        st->arr = (T*)malloc(sizeof(T) * 100);
        st->top = 0, st->capacity = 100;
    }
    T Top(Stack* st)
	{
		return st->arr[st->top - 1];
	}
    void Push(Stack* st, T x){
        // 空间满了扩容
        if(st->top == st->capacity)
        {
            st->arr = (T*)realloc(st->arr, sizeof(T) * st->capacity * 2);
            st->capacity *= 2;
        }
        st->arr[st->top++] = x;
    }
    void Pop(Stack* st){
        st->top--;
    }
    bool Empty(Stack* st){
        return st->top == 0;
    }
// arr、top、capacity都为成员变量
    T* arr;
    int top;        // 栈顶
    int capacity;   // 总容量
};

int main()
{
    Stack st;	//st是Stack类的一个对象。用类创建对象的过程,称为类的实例化
    st.Init(&st);   // 访问成员函数(或成员变量)都用.(指针用->)
    st.Push(&st, 1);
    return 0;
}

观察C++的成员函数,参数都有Stack* st,函数内部访问成员变量是都是st->变量,函数调用的参数都有&st,显得有些冗余。C++进行了"大统一",用this表示函数里面的st,然后将冗余部分都隐含了起来。简化后的代码如下:

typedef int T;
struct Stack
{
    void Init(){
        arr = (T*)malloc(sizeof(T) * 100);
        top = 0, capacity = 100;
    }
    /* 实际上代码等同于:
    void Init(Stack* this){
        this->arr = (T*)malloc(sizeof(T) * 100);
        this->top = 0, this->capacity = 100;
    }
    */
    
    T Top()
	{
		return arr[top - 1];
	}
    void Push(T x){
        // 空间满了扩容
        if(top == capacity)
        {
            arr = (T*)realloc(arr, sizeof(T) * capacity * 2);
            capacity *= 2;
        }
        arr[top++] = x;
    }
    void Pop(){
        top--;
    }
    bool Empty(){
        return top == 0;
    }
    T* arr;
    int top;
    int capacity;
};
int main()
{
    Stack st1, st2;
    st1.Init(); // 这里等同于st1.Init(&st1);
    st1.Push(1);
    return 0;
}

this指针

上文已经初步引入this指针,这里再进行简单介绍。
调用成员函数会自动将对象的地址传给this

class Date
{
public:
    void init(int y, int m, int d)
    {
        _year = y;
        _month = m;
        _day = d;
    }
    // // 编译器对上面的函数处理结果如下:
    // void init(Date* this, int y, int m, int d) 
    // {
    //     this->_year = y;
    //     this->_month = m;
    //     this->_day = d;
    // }
    
    void Print()
    {
        printf("%d-%d-%d\n", _year, _month, _day);
        printf("%d-%d-%d\n", this->_year, this->_month, this->_day); //this可以在函数体里显式调用,但你不能将this作为形参
        // this = nullptr; //err this是个常量
        // 严格来说编译器处理后第一个参数应该是Date* const this,this不可修改指向
    }

	// 注意: 静态成员函数中没有this
    static void func() {
        // 没有this指针,不能访问非静态成员
    }
private:
    int _year;
    int _month;
    int _day;
};

int main()
{
    Date d1;
    d1.init(2025, 9, 9); // 编译器处理后的代码:d1.init(&d1, 2025, 9, 9);  会将d1的地址传给this
    d1.Print();          // 编译器处理后的代码:d1.Print(&d1);  
    
    Date* p = &d1;
    p->init(2026, 9, 10);// 指针调用会处理成:p->init(p, 2026, 9, 10);
    p->Print();          // 编译器处理后的代码:p->Print(p);
    return 0;
}

访问限定符

有时候直接修改结构体成员变量会引起一系列bug(比如获取栈顶元素有人可能直接用st.arr[st.top],但有些人喜欢把栈顶top初始化为0,有些人喜欢初始化为-1…),为此我们希望只通过结构体提供的接口(即成员函数)修改,C++便引入了访问限定符public、private、protected (学继承前可以认为privateprotected是一样的),其规则如下:

  1. public 修饰的成员可以在类外面直接访问,private 修饰的成员不可以在类外面直接访问。(所有成员在类内都可直接访问)
  2. 访问权限的作用域是:该访问限定符到下一个访问限定符之间(没有下一个访问限定符就是该访问限定符到类的结尾)
#include<iostream>
#include<cstdlib>
struct A
{
// 一般习惯将成语函数设为公有,成员变量设为私有
public: // 从此行到private之间的成员的访问权限都是public
    int a1;
    char c1;
    void func1() { 
        puts("void func1()"); 
        printf("a1:%d c1:%c a2: %d c2: %c\n", a1, c1, a2, c2); // 类内部可访问所有成员
    }

private: // 从此行到类结尾的成员的访问权限都是private
    int a2;
    char c2;
    void func2() { 
        puts("void func2()"); 
        printf("a1:%d c1:%c a2: %d c2: %c\n", a1, c1, a2, c2);
    }
};

int main()
{
    A t1;
    // a1、c1、func1 可以在类外访问
    t1.a1 = 1;
    t1.c1 = 'a';
    printf("%d %c\n", t1.a1, t1.c1);
    t1.func1();	// 调用时未初始化a2、c2,打印出来可能是乱码
    
    // // a2、c2、func2 不可以在类外访问
    // t1.a2 = 2;
    // t1.c2 = 'z';
    // t1.func2();
    return 0;
}

C++中更喜欢用class代struct,这两者的区别如下:
class默认访问权限为privatestruct默认访问权限为public

class A
{
	//...
}
上述代码编译器会在开头加上private,即:
class A
{
private:
	//...
}
struct B
{
	//...
}
上述代码编译器会在开头加上public,即:
struct B
{
public:
	//...
}

成员函数声明和定义分离

class Stack
{
public:
    void init(int n);
private:
    int* _arr;
    int _top;
    int _capacity;
};

// 需要指定是哪个类里面的成员函数
void Stack::init(int n)
{
    _arr = (int*)malloc(sizeof(int) * n);
    _top = 0;
    _capacity = n;
}

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

类的大小

类的大小只跟成员变量有关,遵循C语言结构体对齐规则。成员函数是公共的,并不直接存储在对象内部,而是存在于代码段中。

typedef int T;
class Stack
{
public:
    void Init(){
        arr = (T*)malloc(sizeof(T) * 100);
        top = 0, capacity = 100;
    }
    T Top()
	{
		return arr[top - 1];
	}
    void Push(T x){
        if(top == capacity)
        {
            arr = (T*)realloc(arr, sizeof(T) * capacity * 2);
            capacity *= 2;
        }
        arr[top++] = x;
    }
    void Pop(){
        top--;
    }
    bool Empty(){
        return top == 0;
    }
private:
    T* arr;
    int top;
    int capacity;
};
int main()
{
    cout << sizeof(Stack) << endl;  // 你可以删除一个成员函数,可以发现输出结果不变

    Stack st1, st2;
    cout << sizeof(st1) << endl;
    cout << sizeof(st2) << endl;

    st1.Init();
    st1.Push(1);

    st2.Init();  // 通过反汇编可以看出st1与st2调用的是同一个Init() 
    st2.Push(2);
    return 0;
}

在这里插入图片描述

类里面若没有成员变量,其大小是1

class A
{};

class B
{
public:
    void func()
    {}
};
int main()
{
    cout << sizeof(A) << endl; // 1
    cout << sizeof(B) << endl; // 1
    return 0;
}

小练习

下面的代码会编译报错?还是运行报错?还是正常运行?

class A
{
public:
    void Print()
    {
        puts("void Print()");
    }
private:
    int _a;
};

int main()
{
    A* p = nullptr;
    p->Print(); 
    return 0;
}

代码能正常运行,Print函数里this指针为空,但没有用this访问成员变量

将上述代码改动一下

class A
{
public:
    void Print()
    {
        cout << _a << endl; // 这里其实是通过隐藏的this访问成员变量
    }
private:
    int _a;
};

int main()
{
    A* p = nullptr;
    p->Print(); // 运行崩溃
    return 0;
}

更多推荐