01C++ 类定义与访问控制(封装)

C++ 类定义与访问控制(封装)

1. 从结构体到类

在 C 语言中,我们用 struct 把相关数据组合在一起。但问题是------任何人都可以直接修改结构体的成员,无法控制数据的合法性。

C++ 的 class 解决了这个问题:把数据和操作数据的方法捆绑在一起,并控制外部对数据的访问权限

cpp 复制代码
// C 风格:数据和操作分离
struct Student {
    char name[50];
    int age;
    double score;
};
// 外部可以直接修改:stu.score = -100;  // 不合理但合法

// C++ 风格:封装,数据私有,通过公有接口访问
class Student {
private:
    std::string name;
    int age;
    double score;
public:
    void set_score(double s) { if (s >= 0) score = s; }
    double get_score() const { return score; }
};

2. class vs struct

对比 class struct
默认访问控制 private public
能否定义成员函数
能否有构造函数/析构函数
能否继承 能 (默认private继承) 能 (默认public继承)
使用惯例 有封装逻辑的复杂类型 单纯的数据聚合

实际上 C++ 中 struct 几乎和 class 一样,唯一的区别是默认访问权限。

3. 访问控制:public / private / protected

访问级别 本类内部 派生类 外部代码
public
protected
private
cpp 复制代码
class Demo {
private:
    int a;        // 只有本类能访问
protected:
    int b;        // 本类和派生类能访问
public:
    int c;        // 谁都能访问
};

封装的原则:成员变量尽量 private,通过 public 的 getter/setter 提供受控访问。

4. 类的构成

一个完整的类通常放在两个文件中:

复制代码
student.h      ← 头文件:类定义(成员变量 + 函数原型)
student.cpp    ← 源文件:成员函数实现
main.cpp       ← 使用类

头文件:类定义

cpp 复制代码
// student.h
#ifndef STUDENT_H_
#define STUDENT_H_

#include <string>

class Student {
private:
    std::string name;
    int age;
    double score;

public:
    // 构造函数与析构函数
    Student(const std::string& n, int a, double s);
    ~Student();

    // 成员函数
    void show() const;
    std::string get_name() const { return name; }  // 内联函数
    int get_age() const { return age; }
    void set_score(double s);
    double get_score() const;
};

#endif

源文件:成员函数实现

cpp 复制代码
// student.cpp
#include <iostream>
#include "student.h"

// 构造函数:使用成员初始化列表
Student::Student(const std::string& n, int a, double s)
    : name(n), age(a), score(s) {
    std::cout << "构造: " << name << std::endl;
}

// 析构函数
Student::~Student() {
    std::cout << "析构: " << name << std::endl;
}

// const 成员函数:承诺不修改对象
void Student::show() const {
    std::cout << name << ", " << age << "岁, 成绩: " << score << std::endl;
}

void Student::set_score(double s) {
    score = s;
}

double Student::get_score() const {
    return score;
}

主程序

cpp 复制代码
#include <iostream>
#include "student.h"

int main() {
    Student stu("Alice", 20, 85.5);
    stu.show();
    stu.set_score(92.0);
    std::cout << "最新成绩: " << stu.get_score() << std::endl;
    
    // stu.score = 100;  // ❌ 错误!score 是 private
    return 0;
}

编译:

bash 复制代码
g++ main.cpp student.cpp -o class_demo

5. 内联成员函数

在类定义中直接实现的函数自动成为内联函数

cpp 复制代码
class Student {
public:
    // 类内定义 → 自动成为内联函数
    std::string get_name() const { return name; }
};

内联函数在编译期 将函数调用替换为函数体代码,避免函数调用的开销。适合实现简单的 getter/setter。也可以显式用 inline 关键字:

cpp 复制代码
inline std::string Student::get_name() const {
    return name;
}

6. 多个对象互不干扰

cpp 复制代码
Student stu1("Alice", 20, 85.5);
Student stu2("Bob", 22, 78.0);
Student stu3("Charlie", 19, 91.5);

stu1.get_name();  // "Alice"
stu2.get_name();  // "Bob"  ← 每个对象独立存储自己的 name

每个对象有自己独立的成员变量副本,互不干扰。

7. 总结

知识点 要点
封装 private 数据 + public 接口 = 受控访问
class vs struct class 默认 private,struct 默认 public
构造函数 同名、无返回值、创建时自动调用
析构函数 ~类名、无参数无返回值、销毁时自动调用
const 成员函数 尾部加 const,不修改对象
内联函数 类内定义自动内联,适合 getter/setter
多文件组织 .h 放定义,.cpp 放实现

互动测验(选择题)

第 1 题:class 的默认访问控制

cpp 复制代码
class Student { int age; };

age 默认是什么?

A. public

B. private

C. protected

D. 取决于编译器

答案:B。class 默认 private,struct 默认 public,这是两者唯一区别。

第 2 题:封装体现的是?

cpp 复制代码
class Student {
private:
    double score;
public:
    void set_score(double s) { score = s; }
    double get_score() const { return score; }
};

A. 继承

B. 封装(数据隐藏 + 公有接口)

C. 多态

D. 函数重载

答案:B

第 3 题:构造函数的特点

A. 有返回值,可以声明为 void

B. 函数名和类名相同,没有返回值,创建对象时自动调用

C. 需要手动调用

D. 只能有一个构造函数

答案:B

第 4 题:关于析构函数,错误的是?

A. 对象销毁时自动调用

B. 函数名是 ~类名

C. 可以有参数

D. 用于释放资源

答案:C。析构函数无参数。

第 5 题:const 成员函数的作用

cpp 复制代码
void show() const;

A. 修饰返回值

B. 修饰函数参数

C. 承诺该函数不会修改对象的数据成员

D. 让函数运行更快

答案:C


练习题

习题 1:创建自己的类

定义一个 Rectangle 类:

cpp 复制代码
class Rectangle {
private:
    double width;   // 宽
    double height;  // 高
public:
    // 构造函数
    Rectangle(double w, double h);
    ~Rectangle();
    
    // 计算面积
    double area() const;
    // 计算周长
    double perimeter() const;
    
    // getter/setter
    double get_width() const;
    double get_height() const;
    void set_width(double w);
    void set_height(double h);
    
    // 显示信息
    void show() const;
};

要求:

  • 使用成员初始化列表初始化 width 和 height
  • 在 setter 中检查宽高必须为正数
  • 创建多个 Rectangle 对象测试

习题 2:分析题

cpp 复制代码
struct Point {
    int x;
    int y;
};

class Circle {
private:
    Point center;
    double radius;
};

问:PointxyCircle 外部可以直接访问吗?Circlecenterradius 呢?为什么?

习题 3:分析题

以下代码有什么问题?

cpp 复制代码
class Logger {
    std::string prefix;
public:
    void log(const std::string& msg) {
        std::cout << "[" << prefix << "] " << msg << std::endl;
    }
};

int main() {
    Logger log;
    log.log("hello");
    return 0;
}

能编译通过吗?prefix 的值是什么?

相关推荐
无籽西瓜a9 小时前
【西瓜带你学Kafka | 第七期】Kafka 日志存储体系:保留清理、消息格式与分段刷新策略(文含图解)
java·分布式·后端·kafka·消息队列·mq
空中海9 小时前
第四章:Maven专家篇 — 企业级实践与 CI/CD 集成
java·maven
lifewange9 小时前
CNode API v1 完整接口文档(JSON 规范整理)
java·前端·json
lee_curry17 小时前
第四章 jvm中的垃圾回收器
java·jvm·垃圾收集器
九转成圣18 小时前
Java 性能优化实战:如何将海量扁平数据高效转化为类目字典树?
java·开发语言·json
SmartRadio18 小时前
ESP32-S3 双模式切换实现:兼顾手机_路由器连接与WiFi长距离通信
开发语言·网络·智能手机·esp32·长距离wifi
laowangpython18 小时前
Rust 入门:GitHub 热门内存安全编程语言
开发语言·其他·rust·github
我叫汪枫19 小时前
在后台管理系统中,如何递归和选择保留的思路来过滤菜单
开发语言·javascript·node.js·ecmascript
_.Switch19 小时前
东方财富股票数据JS逆向:secids字段和AES加密实战
开发语言·前端·javascript·网络·爬虫·python·ecmascript