【C++ 类和对象 基础篇】—— 抽象思维的巅峰舞者,演绎代码的深邃华尔兹

news/2024/12/26 2:49:30 标签: c++, 开发语言, 汇编

C++学习笔记:

C++ 进阶之路__Zwy@的博客-CSDN博客

各位于晏,亦菲们,请点赞关注!

我的个人主页:

_Zwy@-CSDN博客


目录

1、类

1.1、类的定义

1.2、访问限定符

1.2.1、public

1.2.2、private

1.2.3、protected

1.3、struct&&class 

1.4、类域

2、对象

2.1、对象的定义—实例化

2.2、类和对象的关系

2.3、对象大小

2.3.1、决定对象大小的因素

2.3.2、内存对齐

2.3.2、对象大小的计算 

3、this指针 

3.1、this 指针的基本特性

3.2、this指针的使用场景

3.3、this指针的本质

3.4、静态成员函数不含this 指针

3.5、this指针的存储和传递

 4、C语言和C++实现stack对比

4.1、C++实现stack

4.2、C语言实现stack

C++知识总结 


1、类

1.1、类的定义

在 C++ 中,类 是面向对象编程的核心概念之一。它是一种用户定义的数据类型,用于描述一组具有相同特性(属性)和行为(方法)的对象。

类的定义以class关键字开始,后面跟着类名。类名通常采用大写字母开头的驼峰命名法,类的主体部分包含在一对花括号{}中,在花括号内部可以定义成员变量和成员函数,类中定义的变量叫做成员变量,类中定义的函数叫做成员函数,并且可以使用访问修饰符(private、protected、public)来控制成员的访问权限。最后,类的定义以一个分号;结束。例如实现一个简单的类:

// 定义类
class ClassName {
private:
    // 私有成员和函数
    int _privateField;
    void privateMethod();
protected:
    // 受保护成员和函数
    int _protectedField;
    void protectedMethod();
public:
    // 公有成员和函数
    int _publicField;
    void publicMethod();
};

 为了区分成员变量,⼀般来说会在成员变量前加上下划线_,但这并不是C++的语法规定,而是程序员使用的惯例!


定义在类中的成员函数,一般默认为inline内联函数 ,编译器会尝试将这样的函数内联以减少函数调用的开销,但这只是一个建议,编译器可能根据实际情况选择是否执行内联。

class Example {
public:
    void inlineMethod() {       // 默认是 inline
        std::cout << "Inline Method" << std::endl;
    }
};

等价于:

inline void Example::inlineMethod() {
    std::cout << "Inline Method" << std::endl;
}

1.2、访问限定符

在 C++ 中,访问限定符用于控制类的成员(包括数据成员和成员函数)的可访问性。C++ 有三种访问限定符:public(公有)、private(私有)和protected(保护),这些限定符在类的定义中使用,用于指定类的成员在类外部的可访问程度。

访问权限作用域从该访问限定符出现的位置开始直到下⼀个访问限定符出现时为至,如果后面没有访问限定符,作用域就到 }即类结束。

1.2.1、public

public为公有访问限定符,在类定义中,使用public关键字来声明公有成员。可以在类的任何地方以及类的外部访问。例如:

class Example {
public:
    int publicVar;
    void publicMethod() {
        std::cout << "Public Method" << std::endl;
    }
};
int main() {
    Example obj;
    obj.publicVar = 10;         // 类外部可以访问
    obj.publicMethod();         // 类外部可以访问
    return 0;
}

1.2.2、private

private为私有访问限定符,在类定义中,使用private关键字来声明私有成员,只能在类内部访问,类的外部或派生类无法直接访问,通常用于保护类的内部实现细节。例如:

class Example {
private:
    int privateVar;
    void privateMethod() {
        std::cout << "Private Method" << std::endl;
    }
};
int main() {
    Example obj;
    // obj.privateVar = 5;  // 错误:无法直接访问 private 成员
    return 0;
}

 编译器报错信息:

test.cpp: In function ‘int main()’:
test.cpp:8:9: error: ‘int Example::privateVar’ is private
     int privateVar;
         ^
test.cpp:15:10: error: within this context
      obj.privateVar = 5;  // 错误:无法直接访问 private 成员


1.2.3、protected

protected为保护访问限定符,在类定义中,使用protected关键字来声明保护成员,介于 private 和 public 之间。可以被当前类及其派生类访问,但不能被类外部直接访问。常用于继承,派生类可以直接访问基类中的受保护成员。例如:

class Example {
protected:
    int protectedVar;
    void protectedMethod() {
        cout << "protectedMethod" << endl;
    }
};
int main() {
    Example obj;
   // obj.protectedMethod(); // 错误:类外部无法访问 protected 成员
    return 0;
}

 编译器报错信息: 

test.cpp: In function ‘int main()’:
test.cpp:9:10: error: ‘void Example::protectedMethod()’ is protected
     void protectedMethod() {
          ^
test.cpp:15:25: error: within this context
     obj.protectedMethod(); // 错误:类外部无法访问 protected 成员


使用访问限定符的原因和场景:

数据隐藏和封装:通过使用private和protected访问限定符,可以将类的数据成员隐藏起来,只允许通过类提供的公有成员函数来访问和修改数据。这有助于实现数据的封装,防止外部代码随意修改类的内部状态,提高代码的可维护性和安全性。
继承和多态:protected访问限定符在类的继承关系中起到重要作用。它允许派生类访问基类的部分成员,同时又限制了外部对这些成员的访问,有助于实现面向对象编程中的继承和多态特性。注意:一般的成员变量不期望被外界访问,都会修饰为private或者protected ,一般对外提供服务的成员函数都会修饰为public


1.3、struct&&class 

C语言中,struct被用来定义结构体,C++兼容C语言的用法,同时C++中,struct还可以用来定义类

 实现一个以struct关键字定义的类:

struct Example {
    int var;  // 默认是 public
};

 实现一个以class关键字定义的类:

class Example {
    int var;  // 默认是 private
};

class和struct定义类的区别:

class定义的类中成员没有被访问限定符修饰时默认为private,而struct定义的类中成员没有被访问限定符修饰时默认为public


1.4、类域

概念:

在 C++ 中,类域(Class Scope)是指类定义所界定的范围。类的成员(包括成员变量和成员函数)都在这个类域内。

类域的范围:

类域指类内部定义的成员的作用范围,包括:

1、数据成员(字段)。
2、成员函数(方法)。
3、嵌套类。
类域内的成员通常通过访问限定符控制访问权限(public、protected、private)。

class MyClass {
    int privateVar;  // 私有成员:只能在类内部访问
public:
    int publicVar;   // 公有成员:可以从类外部访问
    void method() {
        privateVar = 10;  // 类内部可以访问 private 成员
        publicVar = 20;   // 类内部可以访问 public 成员
    }
};
int main() {
    MyClass obj;
    obj.publicVar = 5;    // 可以访问 public 成员
    // obj.privateVar = 5; // 错误:无法访问 private 成员
    return 0;
}

访问控制与作用域规则:

在类中,作用域和访问控制由访问限定符决定:

访问限定符类内部访问派生类访问类外部访问
private可以不可以不可以
protected可以可以不可以
public可以可以可以

静态成员与类域:

静态成员属于类本身,而不是类的实例,必须在类外初始化。 

class MyClass {
public:
    static int staticVar;  // 静态成员声明
    static void staticMethod() {
        std::cout << "Static Method: " << staticVar << std::endl;
    }
};
// 静态成员的初始化
int MyClass::staticVar = 0;
int main() {
    MyClass::staticVar = 10;      // 通过类名访问
    MyClass::staticMethod();      // 调用静态方法
    return 0;
}

 嵌套类与作用域:

嵌套类的作用域局限于外部类,但可以访问外部类的 private 成员(如果外部类提供了访问机制)。

class Outer {
private:
    int privateVar;
public:
    class Inner {
    public:
        void accessOuter(Outer& o) {
            o.privateVar = 10;  // 访问外部类的 private 成员
        }
    };
};
int main() {
    Outer outer;
    Outer::Inner inner;
    inner.accessOuter(outer);
    return 0;
}

命名空间与类域:

类域的定义通常嵌套在命名空间中,用于防止名称冲突。

namespace _Zwy {
    class MyClass {
    public:
        void method() {
            std::cout << "Inside MyNamespace::MyClass" << std::endl;
        }
    };
}
int main() {
    _Zwy::MyClass obj;
    obj.method();
    return 0;
}

2、对象

对象是面向对象编程(OOP)中的一个核心概念,是一个类的实例。对象将数据和操作(行为)结合在一起,表示真实世界中的某个具体实体或抽象概念。类的实例化创建出来的就是对象!

2.1、对象的定义—实例化

类是模板,对象是实例:类定义了某种类型的结构和行为,而对象是按照类的模板创建出来的实际实例。
对象的组成:属性(数据成员):对象的状态(由类中的字段定义)。
行为(成员函数):对象的操作能力(由类中的方法定义)。 

class Car {
public:
    string brand;  // 属性:品牌
    int speed;     // 属性:速度
    void drive() {  // 行为:驾驶
        std::cout << brand << " is driving at " << speed << " km/h." << std::endl;
    }
};
int main() {
    Car car;              // 创建对象 car1
    car.brand = "Toyota"; // 设置属性
    car.speed = 100;      // 设置属性
    car.drive();          // 调用行为
    return 0;
}

特点:

唯一性:每个对象在内存中是独立存在的,具有唯一的地址。
封装性:对象隐藏其内部实现,通过方法(行为)与外界交互。 


2.2、类和对象的关系

类是抽象的,定义了一种类型的结构和行为。对象是具体的,实例化类后在内存中创建。

class Animal {
public:
    string name;
    int age;
    void eat() {
        std::cout << name << " is eating." << std::endl;
    }
};
int main() {
    Animal dog;         // 对象:dog
    dog.name = "Buddy";
    dog.age = 3;
    dog.eat();          // 调用方法
    return 0;
}

 多个对象可以由同一个类创建,每个对象都有自己的属性值。

class Rectangle {
public:
    int width, height;
    int area() {
        return width * height;
    }
};
int main() {
    Rectangle rect1, rect2;
    rect1.width = 5; rect1.height = 10;
    rect2.width = 3;  rect2.height = 6;
    std::cout << "Area of rect1: " << rect1.area() << std::endl;
    std::cout << "Area of rect2: " << rect2.area() << std::endl;
    return 0;
}

 输出:

Area of rect1: 50
Area of rect2: 18

2.3、对象大小

在C++中,对象的大小是一个与内存管理息息相关的重要概念。作为类的实例,对象不仅封装了数据,还反映了程序运行时的内存占用情况。对象的大小并不仅仅是简单地将类中非静态成员变量的大小相加,还受到内存对齐、填充字节以及类中是否包含虚函数等因素的影响

2.3.1、决定对象大小的因素

对象的大小只包括类中非静态数据成员(成员变量)所占的内存空间:

静态数据成员、成员函数以及虚函数的代码(包括它们的地址)都不包含在对象本身的大小中
成员函数:
成员函数在编译阶段会被编译成代码段中的独立函数,所有实例共享一份函数代码。因此,成员函数并不占用每个对象的存储空间。
虚函数表指针(对于多态类):
如果类包含虚函数,编译器通常会在对象中存储一个指向虚函数表(vtable)的指针,用于实现运行时多态。
这种情况下,对象的大小会增加一个指针的大小(通常是 4 字节或 8 字节,取决于平台)。
静态成员变量:
静态成员变量属于类而不属于某个对象,因此也不包含在对象大小中
内存对齐:
编译器会为数据成员添加填充字节,以确保数据对齐符合系统要求。


对象大小的计算涉及到内存对齐,我们先来理解什么是内存对齐?

2.3.2、内存对齐

为什么需要内存对齐?
CPU 从内存读取数据时,并不是逐字节读取,而是按一定的 字节块(如 4 字节或 8 字节) 读取。如果数据不对齐,CPU 需要执行额外的操作来读取数据,影响性能。

内存对齐的规则:

第⼀个成员在与结构体偏移量为0的地址处。
其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
注意:对齐数 = 编译器默认的⼀个对齐数与该成员大小的较小值。
VS中默认的对齐数为8
结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍。

示例:

struct Example {
    char a;     // 1 字节
    int b;      // 4 字节
    short c;    // 2 字节
};
int main() {
    std::cout << "Size of Example: " << sizeof(Example) << std::endl;
    return 0;
}

输出:

Size of Example: 12

 内存布局:

成员字节占用备注
char a11 字节
填充3填充到 4 字节对齐
int b44 字节对齐
short c22 字节
填充2填充到 4 字节整数倍

总大小:1 + 3 + 4 + 2 + 2 = 12 字节


调整成员顺序减少填充:

struct Optimized {
    char a;     // 1 字节
    short c;    // 2 字节
    int b;      // 4 字节
};
int main() {
    std::cout << "Size of Optimized: " << sizeof(Optimized) << std::endl;
    return 0;
}

 输出:

Size of Optimized: 8

优化后的内存布局:

成员字节占用备注
char a11 字节
short c2紧接着占 2 字节
填充1填充到 4 字节对齐
int b44 字节对齐

总大小:1 + 2 + 1 + 4 = 8 字节

通过调整成员的顺序,结构体的大小从 12 字节 减少到了 8 字节。


嵌套结构体的内存对齐:

嵌套结构体的内存对齐规则
嵌套结构体的每个成员仍然遵循自身的对齐规则。
外部结构体会将嵌套结构体视为一个整体,嵌套结构体的大小和对齐要求会影响外部结构体的内存布局。
外部结构体的大小必须是所有成员的最大对齐系数的整数倍,包括嵌套结构体的对齐。 

简单的嵌套结构体:

struct Inner {
    char a;   // 1 字节
    int b;    // 4 字节
};
struct Outer {
    char x;       // 1 字节
    Inner inner;  // 嵌套结构体
    short y;      // 2 字节
};
int main() {
    std::cout << "Size of Inner: " << sizeof(Inner) << " bytes" << std::endl;
    std::cout << "Size of Outer: " << sizeof(Outer) << " bytes" << std::endl;
    return 0;
}

输出:

Size of Inner: 8 bytes
Size of Outer: 16 bytes

内存布局分析:

成员字节偏移量占用字节说明
char x01成员 x 占 1 字节
填充13填充 3 字节,使下一个成员按 4 字节对齐
Inner(嵌套结构体)48Inner 的大小为 8 字节(按 4 字节对齐)
short y122成员 y 占 2 字节
填充142末尾填充,使结构体总大小按 4 字节对齐

总大小:1 + 3 + 8 + 2 + 2 = 16 字节


优化后的结构体(成员顺序调整) 

struct OuterOptimized {
    char x;       // 1 字节
    short y;      // 2 字节
    Inner inner;  // 嵌套结构体
};

内存布局分析: 

成员字节偏移量占用字节说明
char x01成员 x 占 1 字节
short y22成员 y 占 2 字节
填充44填充 4 字节,使下一个成员按 8 字节对齐
Inner(嵌套结构体)88Inner 的大小为 8 字节(按 4 字节对齐)

总大小:1 + 2 + 1 + 8 = 12 字节,按最大对齐 4 字节,最终为 16 字节。


多层嵌套结构体:

struct Level1 {
    char a;    // 1 字节
    int b;     // 4 字节
};
struct Level2 {
    Level1 l1; // 8 字节
    double d;  // 8 字节
};
struct Level3 {
    char x;       // 1 字节
    Level2 l2;    // 16 字节
};

内存布局分析: 

成员字节偏移量占用字节说明
char x01成员 x 占 1 字节
填充17填充 7 字节,使下一个成员按 8 字节对齐
Level2 l2816嵌套结构体 Level2 的大小为 16 字节

总大小:1 + 7 + 16 = 24 字节 


自定义对齐规则

C++ 提供了 #pragma pack 指令和 alignas 关键字,允许我们自定义内存对齐方式。

使用 #pragma pack 控制对齐

#pragma pack(1)  // 设置对齐字节为 1
struct Example {
    char a;
    int b;
    short c;
};
#pragma pack()  // 恢复默认对齐
int main() {
    std::cout << "Size of Example: " << sizeof(Example) << std::endl;
    return 0;
}

 #pragma pack(1) 表示按 1 字节对齐,不会添加填充字节,但可能降低性能。

输出:

Size of Example: 7

使用 alignas 控制对齐 

struct Example {
    alignas(8) char a;  // 强制 8 字节对齐
    int b;
};
int main() {
    std::cout << "Size of Example: " << sizeof(Example) << std::endl;
    return 0;
}

输出:

Size of Example: 8

2.3.2、对象大小的计算 

仅包含非静态数据成员:

class MyClass {
    int a;      // 4 bytes
    char b;     // 1 byte (but padded to 4 bytes for alignment)
    double c;   // 8 bytes
};
int main() {
    std::cout << "Size of MyClass: " << sizeof(MyClass) << " bytes" << std::endl;
    return 0;
}

输出:

Size of MyClass: 16 bytes

包含静态成员:

class MyClass {
    int a;              // 4 bytes
    static int b;       // 静态成员,不计入对象大小
};

int main() {
    std::cout << "Size of MyClass: " << sizeof(MyClass) << " bytes" << std::endl;
    return 0;
}

输出: 4 字节(只包括 int a 的大小)

Size of MyClass: 4 bytes

空类:

class Empty {};
int main() {
    std::cout << "Size of Empty: " << sizeof(Empty) << " bytes" << std::endl;
    return 0;
}

输出C++ 要求每个对象在内存中有唯一地址,因此即使空类的大小为 1 字节。

Size of Empty: 1 bytes

完整示例:

class Test {
    int a;             // 非静态数据成员
    static int b;      // 静态数据成员
    void func() {}     // 成员函数
    virtual void vfunc() {} // 虚函数
};
int main() {
    std::cout << "Size of Test: " << sizeof(Test) << " bytes" << std::endl;
    return 0;
}

输出:sizeof(Test) 只包括非静态数据成员a的大小和虚函数表指针大小,成员函数 func() 和虚函数代码不影响对象大小,它们是共享的代码段。

Size of Test: 8 bytes

 内存对齐的影响:

class MyClass1 {
    char a;   // 1 byte
    int b;    // 4 bytes
    char c;   // 1 byte
};
class MyClass2 {
    char a;   // 1 byte
    char c;   // 1 byte
    int b;    // 4 bytes
};
int main() {
    std::cout << "Size of MyClass1: " << sizeof(MyClass1) << " bytes" << std::endl;
    std::cout << "Size of MyClass2: " << sizeof(MyClass2) << " bytes" << std::endl;
    return 0;
}

输出: 

Size of MyClass1: 12 bytes
Size of MyClass2: 8 bytes

虚函数的影响:(继承和多态会讲到)

如果类中包含虚函数,每个对象会多出一个指针(通常是 4 或 8 字节,取决于系统架构),指向虚函数表(V-Table)。

输出int a 占 4 字节。虚函数表指针占 4 字节(在 32 位系统)或 8 字节(在 64 位系统)

Size of MyClass: 8 bytes

3、this指针 

在 C++ 中,this 是一个特殊的指针,指向当前对象本身。它是非静态成员函数的一个隐式参数,只有在调用类的非静态成员函数时,this 指针才会被传递给函数。简单来说,this 指针表示调用该函数的具体对象的地址。

3.1、this 指针的基本特性

this 是一个指针:指向当前对象的内存地址。
隐式传递:this 指针是由编译器自动提供的,无需显式传递。
只能用于非静态成员函数:静态成员函数不属于任何具体对象,因此没有 this 指针。
常量性:this 指针本身是不可修改的,它只能指向当前对象。


3.2、this指针的使用场景

区分成员变量和局部变量/参数:

当成员变量与函数参数或局部变量同名时,可以使用 this 指针来明确指向成员变量。

class MyClass {
private:
    int x;
public:
    void setX(int x) {     // 参数和成员变量同名
        this->x = x;       // 使用 this 指针区分成员变量和参数
    }
    void display() {
        std::cout << "x = " << this->x << std::endl;
    }
};
int main() {
    MyClass obj;
    obj.setX(10);      // 设置成员变量 x
    obj.display();     // 输出:x = 10
    return 0;
}

输出: 在 setX 函数中,this->x 明确表示成员变量 x,而参数 x 覆盖了同名的成员变量。

x = 10

返回当前对象自身(实现链式调用):

this 指针可以返回当前对象的引用,实现链式调用。

class MyClass {
private:
    int value;
public:
    MyClass& setValue(int v) {   // 返回当前对象的引用
        this->value = v;
        return *this;            // *this 解引用指向当前对象
    }
    void display() {
        std::cout << "Value = " << value << std::endl;
    }
};
int main() {
    MyClass obj;
    obj.setValue(10).setValue(20).display();  // 链式调用
    return 0;
}

输出:setValue 返回 *this(当前对象的引用),使调用者可以连续调用其他方法。

Value = 20

3.3、this指针的本质

 在底层实现中,this 指针是通过编译器隐式传递给非静态成员函数的

class MyClass {
public:
    void func() {
        std::cout << "Function called" << std::endl;
    }
};
int main() {
    MyClass obj;
    obj.func();
    return 0;
}

如上代码,实际上被编译器转换为:(不能在实参和形参的位置显示的写this指针(编译时编译器会处理),但是可以在函数体内显示使用this指针

void MyClass::func(MyClass* this) {   // 编译器自动传递 this 指针
    std::cout << "Function called" << std::endl;
}

int main() {
    MyClass obj;
    MyClass::func(&obj);  // 隐式传递 this 指针
    return 0;
}

编译器会自动将 this 指针传递给成员函数,因此在函数内部可以访问调用对象的成员。


3.4、静态成员函数不含this 指针

静态成员函数属于类本身,而不属于某个具体对象。因此,它们不需要 this 指针。


class MyClass {
public:
    static void staticFunc() {
        // std::cout << this;  // 错误:静态函数没有 this 指针
        std::cout << "Static function called." << std::endl;
    }
};
int main() {
    MyClass::staticFunc();
    return 0;
}

输出:

Static function called.

静态函数无法访问非静态数据成员,因为它们不依赖于具体对象,没有this指针


3.5、this指针的存储和传递

this 指针的存储位置:
this 指针并不是一个变量,而是由编译器在函数调用时隐式传递给非静态成员函数的一个指针。在对象的内存中,this 指针并不占据实际空间,它只是一个调用函数时的临时指针。
在函数调用栈上,this 指针作为一个隐藏参数存储于栈内,具体存储位置取决于编译器实现与调用约定


this 指针的传递机制:
在调用非静态成员函数时,编译器会将调用对象的地址传递给该函数,生成的机器代码会将对象地址存储到 this 指针中。
一般来说,this 指针存储在寄存器中(如 ECX 寄存器,在 x86 的 cdecl 调用约定 下),也可能存储在栈内存中,具体取决于平台和编译器


this 指针的隐式传递:

class MyClass {
public:
    void display() {
        std::cout << "Address of this: " << this << std::endl;
    }
};
int main() {
    MyClass obj;
    std::cout << "Address of obj: " << &obj << std::endl;
    obj.display();
    return 0;
}

输出:在调用 obj.display() 时,this 指针被隐式传递给 display() 函数。在函数内部,this 指向当前对象 obj 的地址。4\

Address of obj: 00A5FBAF
Address of this: 00A5FBAF

this 指针存储的具体过程:
调用栈分配:   

当调用非静态成员函数时,调用对象的地址被压入栈,作为 this 指针传递给函数.

寄存器优化:

在某些平台上,为了优化性能,this 指针会存储在特定的寄存器中(例如 x86 平台的 ECX 寄存器)。

编译器生成代码:

编译器会在函数调用时,将对象地址传递给函数中的 this 指针,确保其在函数内部可用


汇编层面理解:

假设使用 x86 架构,编译器生成的汇编代码会将 this 指针存储在 ECX 寄存器中:

C++:

class MyClass {
public:
    void func() {
        std::cout << "Function called" << std::endl;
    }
};
int main() {
    MyClass obj;
    obj.func();
    return 0;
}

汇编:(简化)

; obj.func() 的调用过程
mov ecx, OFFSET obj    ; 将 obj 的地址存储到 ECX(this 指针)
call MyClass::func     ; 调用 func,this 指针已传入

在调用 func() 之前,编译器会将 obj 的地址存储到寄存器 ECX。当 func 执行时,this 指针指向 obj。 


 4、C语言和C++实现stack对比

面向对象编程(Object-Oriented Programming, OOP)的三大特点是封装、继承和多态。这三个特性是 OOP 的核心思想,它们通过抽象、模块化和代码复用性,使得程序更易于设计、维护和扩展。通过下面的对比,我们可以先感受一下封装的魅力!

封装的本质:

封装是面向对象编程(OOP)的核心特性之一,强调将数据和操作封装在一起,通过控制访问权限实现信息隐藏和接口抽象。封装的本质在于保护对象的内部状态,同时为外部提供受控的访问接口。

核心点: 

信息隐藏:类的内部细节对外部不可见。
访问控制:通过访问修饰符(private, protected, public)限制外部对类成员的访问。
对外提供接口:通过方法访问或修改内部数据,保证对象状态的完整性和安全性。
 

 C++中数据和函数都封装到了类里面,通过访问限定符进行了、限制,不能再随意通过对象直接访问数据,这是C++封装的⼀种体现,这个是最重要的变化。这里的封装的本质是⼀种更严格规范的管理,避免出现乱访问修改的问题。当然封装不仅仅是这样的,我们后面还需要不断的去习。 

4.1、C++实现stack

#include<iostream>
#include<assert.h>
using namespace std;
typedef int STDataType;
class Stack
{
public:
	// 成员函数
	void Init(int n = 4)
	{
		_a = (STDataType*)malloc(sizeof(STDataType) * n);
		if (nullptr == _a)
		{
			perror("malloc failed");
			return;
		}
		_capacity = n;
		_top = 0;
	}
    void Push(STDataType x)
	{
		if (_top == _capacity)
		{
			int newcapacity = _capacity * 2;
			STDataType* tmp = (STDataType*)realloc(_a, newcapacity *
				sizeof(STDataType));
			if (tmp == NULL)
			{
				perror("realloc failed");
				return;
			}
			_a = tmp;
			_capacity = newcapacity;
		}
		_a[_top++] = x;
	}
	void Pop()
	{
		assert(_top > 0);
		--_top;
	}
	bool Empty()
	{
		return _top == 0;
	}
	int Top()
	{
		assert(_top > 0);
		return _a[_top - 1];
	}
	void Destroy()
	{
		free(_a);
		_a = nullptr;
		_top = _capacity = 0;
	}
private:// 成员变量
	STDataType* _a;
	size_t _capacity;
	size_t _top;
};

4.2、C语言实现stack

#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>
typedef int STDataType;
typedef struct Stack
{
	STDataType* a;
	int top;
	int capacity;
}ST;
void STInit(ST* ps)
{
	assert(ps);
	ps->a = NULL;
	ps->top = 0;
	ps->capacity = 0;
}
void STDestroy(ST* ps)
{
	assert(ps);
	free(ps->a);
	ps->a = NULL;
	ps->top = ps->capacity = 0;
}
void STPush(ST* ps, STDataType x)
{
	assert(ps);// 满了, 扩容
	if (ps->top == ps->capacity)
	{
		int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
		STDataType* tmp = (STDataType*)realloc(ps->a, newcapacity *
			sizeof(STDataType));
		if (tmp == NULL)
		{
			perror("realloc fail");
			return;
		}
		ps->a = tmp;
		ps->capacity = newcapacity;
	}
	ps->a[ps->top] = x;
	ps->top++;
}
bool STEmpty(ST* ps)
{
	assert(ps);
	return ps->top == 0;
}
void STPop(ST* ps)
{
	assert(ps);
	assert(!STEmpty(ps));
	ps->top--;
}
STDataType STTop(ST* ps)
{
	assert(ps);
	assert(!STEmpty(ps));
	return ps->a[ps->top - 1];
}
int STSize(ST* ps)
{
	assert(ps);
	return ps->top;
}

C 语言:

没有类和对象的概念,只能通过结构体struct和函数的组合来实现栈。
数据和操作是分开的,封装性较差。
需要显式传递结构体指针,操作数据。


C++:

提供类(class) 和 对象 的概念,将数据和行为封装在一起。

通过成员函数操作栈,具有更好的封装性和安全性。

构造函数和析构函数自动管理内存(new/delete)


C++知识总结 

1、类:自定义类型,访问限定符控权限,struct 与 class 有别,有类域概念。
2、对象:类实例化产物,大小受成员变量及内存对齐影响,体现类的具体化。
3、this 指针:非静态成员函数里指向当前对象,用于区分变量,静态函数无,本质特殊且有存储传递方式。
4、C 与 C++ 实现 stack:C++ 靠类封装,清晰安全;C 语言用结构体与函数指针,灵活但手动管理内存且耦合度高。

在 C++ 中,类与对象是根基。它们奠定了 C++ 世界的基石,是开启编程进阶之路的必经之门,本文主要讲解最基本的类和对象的定义,在进阶部分会对类和对象进行更深层次的剖析,为后续C++飞升之路打造最有力的制胜法宝!

如上的讲解只是我的一些拙见,如有不足之处,还望各位大佬不吝在评论区予以斧正,感激不尽!创作不易,还请多多互三支持!你们的支持是我最大的动力!


http://www.niftyadmin.cn/n/5799670.html

相关文章

仓颉编程语言深入教程:基础概念和数据类型

benwne 一、标识符1. 标识符命名规则2. 示例代码3. 提示 二、程序结构1. 变量与常量的定义2. 条件语句3. 循环结构4. 模块与包管理 三、表达式1. 算术表达式2. 关系表达式3. 逻辑表达式4. 赋值表达式 四、函数1. 定义函数2. 可选参数与默认值3. 匿名函数与箭头函数4. 高阶函数5…

5QI DSCP映射

5QI DSCP映射 概念定义5QIDSCP5QI 调度策略REF5QI定义在3GPP规范23.501中,用于指示一个5G QoS参数集,这些参数用于控制QoS流转发处理,最终为用户数据提供不同优先级、不同可靠性、不同时延等方面的服务体验。概念定义 5QI 3GPP TS 23.501 第 5.7.4 节提供了标准化的 5QI(…

mongodb给不同的库设置不同的密码进行连接

默认的数据库安装之后是没有密码的&#xff0c;是可以直接访问的&#xff0c;但是如果端口不小心暴露出去了&#xff0c;就会存在很大的安全隐患。本节课教大家如何给mongodb设置账号密码进行访问。 设置管理员并密码登录 查看服务 默认安装好的mongodb会自动创建好服务&…

C语言 递归与迭代

练习1 求n的阶乘 递归实现 迭代实现 练习2 求第n个斐波那契数 斐波那契数列 1 1 2 3 5 8 13 21 34 55... 用递归解决&#xff08;效率慢&#xff09;计算重复 迭代方式&#xff08;速度快&#xff0c;效率快&#xff09; 递归的经典题目 1.汉诺塔问题 2.青蛙跳台阶问题…

Docker pull images Error

Docker pull images Error 欢迎使用Markdown编辑器dockercontainerd 欢迎使用Markdown编辑器 我在Pull 镜像时遇到的错误 http: server gave HTTP response to HTTPS client &#xff0c;以下针对docker和containerd解决方法。 docker "insecure-registries": [&qu…

企业数字化转型加速,现代 IT 如何用 Datadog 全面提升可观测性?

作为 Gartner 可观测平台魔力象限的领导者&#xff0c;Datadog 凭借全面的功能、直观的用户界面和强大的产品路线图赢得了全球企业的信任。 企业 IT 架构正变得日益复杂&#xff0c;从本地服务器到云端部署&#xff0c;从单体应用向微服务&#xff0c;还有容器、 Kubernetes 等…

观察者模式和发布-订阅模式有什么异同?它们在哪些情况下会被使用?

大家好&#xff0c;我是锋哥。今天分享关于【观察者模式和发布-订阅模式有什么异同&#xff1f;它们在哪些情况下会被使用&#xff1f;】面试题。希望对大家有帮助&#xff1b; 观察者模式和发布-订阅模式有什么异同&#xff1f;它们在哪些情况下会被使用&#xff1f; 1000道 …

Docker Build 命令详解:在 Ubuntu 上构建 Docker 镜像教程

简介 Docker 通过提供轻量级、可移植和高效的解决方案&#xff0c;彻底改变了软件开发和部署。docker build 命令是 Docker 镜像创建过程的核心。本文将探讨 docker build 命令、其语法、用法以及优化 Docker 构建的最佳实践。本教程的目标是手把手教你如何在 Linux 服务器上使…