C++封装

C++封装

C++相比其语言来说,C++是面向对象的语言,首先如果想要面向对象,就得想办法把信息封装起来,成为一个,我们今天来看C++引入封装之后的一些变化:

C++中的类

C++中一般会使用class来表示类:

cpp 复制代码
#include <iostream>
#include <string>

class Person {
private:
    std::string name;  // 私有成员变量
    int age;

public:
    // 公共方法:设置姓名(接口)
    void setName(const std::string& newName) {
        if (!newName.empty()) {
            name = newName;
        }
    }

    // 公共方法:获取姓名(接口)
    std::string getName() const {
        return name;
    }

    // 公共方法:设置年龄(接口)
    void setAge(int newAge) {
        if (newAge >= 0 && newAge <= 150) {  // 数据合法性校验
            age = newAge;
        }
    }

    // 公共方法:获取年龄(接口)
    int getAge() const {
        return age;
    }
};

int main() {
    Person person;
    person.setName("Alice");
    person.setAge(25);

    std::cout << "Name: " << person.getName() << std::endl;  // 通过接口访问
    std::cout << "Age: " << person.getAge() << std::endl;

    // person.name = "Bob";  // 错误!name 是私有成员,外部无法直接访问
    return 0;
}

class如果不写访问限定符,默认成员都是私有成员 。这和struct有点区别,struct如果不写,成员默认都是公有成员

1. 默认访问权限

  • class 的成员(变量/函数)默认是 private 的。
  • struct 的成员默认是 public 的。
示例:
cpp 复制代码
// class 示例
class MyClass {
    int x;  // 默认是 private
    void foo(); // 默认是 private
};

// struct 示例
struct MyStruct {
    int x;  // 默认是 public
    void bar(); // 默认是 public
};

2. 默认继承权限

  • class 的默认继承方式是 private 继承。
  • struct 的默认继承方式是 public 继承。
示例:
cpp 复制代码
// class 继承
class Base {};
class Derived : Base {}; // 默认是 private 继承

// struct 继承
struct Base {};
struct Derived : Base {}; // 默认是 public 继承

其他说明

  • 功能完全一致 :除了上述两点,classstruct 在功能上没有区别。
    • 两者都可以包含成员函数、构造函数、析构函数、继承、多态等。
    • 两者都可以使用 public/private/protected 访问修饰符。
  • 使用场景的惯例
    • class :通常用于需要封装和数据隐藏的复杂对象(例如 BankAccount)。
    • struct :通常用于简单的数据聚合(例如坐标点 Point { int x, y; })或与 C 兼容的代码。

总结

特性 class struct
默认访问权限 private public
默认继承权限 private public
典型用途 封装复杂逻辑和数据 简单数据聚合或兼容 C 代码

代码示例

cpp 复制代码
// 使用 class(强调封装)
class BankAccount {
private:
    double balance; // 私有成员,外部无法直接访问

public:
    void deposit(double amount) { 
        if (amount > 0) balance += amount; 
    }
    double getBalance() const { return balance; }
};

// 使用 struct(强调数据公开)
struct Point {
    int x; // 默认 public
    int y; 
    void print() { std::cout << "(" << x << ", " << y << ")"; }
};

类的两种定义方式

这里其实就是定义和声明是否分离:

cpp 复制代码
class Person
{
public:
	void setName();
private:
	std::string _name;
};

void Person::setName()
{
	std::cout << "设置姓名" << std::endl;
}
cpp 复制代码
class Person
{
public:
	void setName()
	{
		std::cout << "设置姓名" << std::endl;
	}
private:
	std::string _name;
};

声明和定义全部放在类体中,需注意:成员函数如果在类中定义,编译器可能会将其当成内联函数处理

内联函数

内联函数是C++中的一种函数类型,它通过在编译时将函数的代码"内联"到调用者代码中,来减少函数调用带来的开销。意味着如果一个函数被声明为内联函数的话,会在每个调用该函数的地方直接展开其代码而不会进行复杂的跳转。

inline

如果想声明一个函数为内联函数,可以使用关键字inline:

cpp 复制代码
inline int add(int a, int b) {
    return a + b;
}

但是请注意,这个函数最终是否真的成为内联函数,是由编译器决定的,如果一个函数被声明为内联函数,但是这个函数实际上又很大。编译器会自动忽视inline关键字。同时,如果这个函数适合声明为inline但实际上又没有声明为inline,编译器也会自动把他声明为inline。

类对象的大小

对于类对象来说,其大小由成员变量(包括继承来的成员)所占的空间决定,但不包括成员函数,因为成员函数不会影响类对象的大小------它们在内存中的存储方式与对象实例无关。

以下是几点需要注意的地方:

  1. 成员变量:类对象的大小主要取决于它的非静态成员变量。静态成员变量和成员函数不计入对象的大小,因为它们属于类本身而不是某个特定的对象实例。

  2. 内存对齐:C++编译器会对数据结构进行内存对齐,这意味着实际占用的内存可能会比所有成员变量简单相加的结果要大。不同的平台有不同的对齐要求,这会影响最终的大小。

  3. 空类:即使是空类,它也会至少占用1个字节的空间以确保每个对象都有独一无二的地址。

  4. 虚函数表指针(vptr):如果类中有虚函数,则该类的对象会包含一个指向虚函数表(vtable)的隐藏指针(vptr),这通常会增加对象的大小(在常见的实现中是增加指针的大小,例如在64位系统上增加8字节)。

下面是一个简单的例子:

cpp 复制代码
class Person
{
public:
	void setName()
	{
		std::cout << "设置姓名" << std::endl;
	}
private:
	//std::string _name;
	int _id;
};

int main()
{
	Person p;

	std::cout << sizeof(p) << std::endl;
}

要准确理解一个具体类的大小,了解编译器的内存对齐规则是很重要的。不同的编译器和平台可能有不同的默认对齐设置,也可以通过编译器选项或预处理指令(如#pragma pack)来调整这些设置。

#pragma pack

#pragma pack 是一个编译器指令,用于设定结构体(struct)和类(class)成员的对齐方式。在默认情况下,编译器会按照一定的规则对数据进行内存对齐,以提高访问速度。然而,在某些情况下,你可能希望改变这种默认行为,例如为了节省空间或者与特定硬件或外部协议的数据格式相匹配。这时就可以使用 #pragma pack 指令。

语法

cpp 复制代码
#pragma pack(push)  // 保存当前对齐设置
#pragma pack(n)     // 设置新的对齐值为n字节,n可以是1, 2, 4, 8等
// 在这里定义你的结构体或类
#pragma pack(pop)   // 恢复之前保存的对齐设置

这里的 n 表示每个成员将按照 n 字节的边界进行对齐。例如,如果设置为 #pragma pack(1),则所有成员将以1字节边界对齐,从而避免任何填充字节,但这可能会牺牲一些访问速度。

示例

cpp 复制代码
#pragma pack(push)
#pragma pack(1)
struct ExampleStruct {
    char a;    // 1 byte
    int b;     // 4 bytes, 不进行4字节对齐,直接跟在a后面
    short c;   // 2 bytes
};
#pragma pack(pop)

在这个例子中,没有使用默认的对齐方式,而是强制每个成员紧挨着前一个成员存储,不论其类型通常需要怎样的对齐。这会导致更紧凑的数据布局,但可能会影响性能,因为处理器访问未对齐的数据效率较低。

this指针

this指针是C++中一个隐式的指针,它指向调用成员函数的对象。每个非静态成员函数都有这个隐式的this指针,它作为第一个参数自动传递给成员函数,用于访问调用该函数的对象的成员变量和其它成员函数。通过this指针,成员函数可以知道自己被哪个对象调用,并据此操作该对象的数据。

举个例子:

cpp 复制代码
class Person
{
public:
	void setName(std::string name)
	{
		this->name = name;
	}
private:
	std::string name;
};

int main()
{
	Person p;

	p.setName("老张");

}

this指针的特性

this指针在C++中具有几个重要的特性,这些特性影响着它在类成员函数中的使用方式和作用。以下是this指针的主要特性:

  1. 隐式传递 :每当一个成员函数被调用时,编译器会自动将指向调用该函数的对象的指针(即this指针)作为第一个参数传递给该函数。这意味着你不需要显式地传递这个指针。

  2. 类型与常量性

  • 在普通成员函数中,this是一个指向当前对象类型的指针。例如,如果有一个类Example,那么在它的成员函数中,this的类型将是Example* const,表示一个指向当前对象的常量指针(不能改变指针所指向的对象)。
  • 在常量成员函数中,this指针的类型是const Example* const,意味着既不能通过this修改对象的状态,也不能修改this本身指向哪个对象。这确保了常量成员函数不会修改对象的数据成员(除非这些成员被声明为mutable)。
  1. 访问成员变量和函数 :通过this指针可以访问调用成员函数的对象的所有非静态成员变量和成员函数。当你在成员函数内部需要明确区分成员变量和同名的局部变量或参数时,this指针非常有用。

  2. 返回当前对象支持链式编程this指针经常用于实现链式编程(也称为方法链),即让成员函数返回当前对象的引用(*this),从而允许连续调用多个成员函数。

cpp 复制代码
    class Example {
    public:
        Example& doSomething() {
            // 执行某些操作...
            return *this; // 返回当前对象的引用以支持链式调用
        }
    };
  1. 不可更改 :虽然this指针本身是一个常量指针(其值不能被修改),但它指向的对象内容(除非在常量成员函数中)是可以被修改的。也就是说,你不能使this指向另一个对象,但可以通过this修改其所指向对象的内容。

  2. 不适用于静态成员函数 :静态成员函数不属于任何特定的对象实例,因此它们没有this指针。如果你尝试在静态成员函数中使用this指针,会导致编译错误。

理解这些特性对于有效利用this指针以及编写高效、清晰的C++代码至关重要。this指针提供了一种直接访问对象数据的方式,并且支持一些高级编程技巧如链式调用等。

this指针存在哪?

this指针并没有实际的存储体,它是代码在运行时自动构建的传给类中非静态成员函数的一个隐藏参数。

this指针可以为空吗?

理论上是可以的,但是这是未定义行为:

cpp 复制代码
class Person
{
public:
	void setName(std::string name)
	{
		
	}
private:
	std::string name;
};

int main()
{
	Person* p = nullptr;

	p->setName("老庄");
}

运行这行代码不会报错,但是如果加上一行:

cpp 复制代码
class Person
{
public:
	void setName(std::string name)
	{
		this->name = name;
	}
private:
	std::string name;
};

int main()
{
	Person* p = nullptr;

	p->setName("老庄");
}

程序会报错,这是因为第一段代码中我们并没有去尝试访问类中的成员变量,所以不会报错。如果有尝试访问类中成员变量的操作,就会报错。应为这个变量根本就没有地址。无法通过地址访问。

相关推荐
╰つ゛木槿1 小时前
Spring Boot 调用DeepSeek API的详细教程
java·spring boot·后端·deepseek
*.✧屠苏隐遥(ノ◕ヮ◕)ノ*.✧1 小时前
C语言_数据结构总结8:链式队列
c语言·开发语言·数据结构·链表·visualstudio·visual studio
千里码aicood1 小时前
[含文档+PPT+源码等]精品基于Python实现的校园小助手小程序的设计与实现
开发语言·前端·python
讨厌下雨的天空1 小时前
C++之list
开发语言·c++·list
大麦大麦2 小时前
深入剖析 Sass:从基础到进阶的 CSS 预处理器应用指南
开发语言·前端·css·面试·rust·uni-app·sass
hhw1991122 小时前
c#面试题整理6
java·开发语言·c#
Icomi_2 小时前
【神经网络】0.深度学习基础:解锁深度学习,重塑未来的智能新引擎
c语言·c++·人工智能·python·深度学习·神经网络
蠟筆小新工程師2 小时前
Deepseek可以通过多种方式帮助CAD加速工作
开发语言·python·seepdeek
程序视点3 小时前
SpringBoot配置入门
java·spring boot·spring
不知道取啥耶3 小时前
C++ 滑动窗口
数据结构·c++·算法·leetcode