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 的值是什么?

相关推荐
二哈赛车手9 小时前
新人笔记---ApiFox的一些常见使用出错
java·笔记·spring
为何创造硅基生物10 小时前
C语言 结构体内存对齐规则(通俗易懂版)
c语言·开发语言
吃好睡好便好10 小时前
在Matlab中绘制横直方图
开发语言·学习·算法·matlab
栗子~~10 小时前
JAVA - 二层缓存设计(本地缓冲+redis缓冲+广播所有本地缓冲失效) demo
java·redis·缓存
星寂樱易李10 小时前
iperf3 + Python-- 网络带宽、网速、网络稳定性
开发语言·网络·python
YDS82910 小时前
DeepSeek RAG&MCP + Agent智能体项目 —— RAG知识库的搭建和接口实现
java·ai·springboot·agent·rag·deepseek
仰泳之鹅10 小时前
【C语言】自定义数据类型2——联合体与枚举
c语言·开发语言·算法
之歆11 小时前
DAY_12JavaScript DOM 完全指南(二):实战与性能篇
开发语言·前端·javascript·ecmascript
未若君雅裁12 小时前
MyBatis 一级缓存、二级缓存与清理机制
java·缓存·mybatis
于小猿Sup12 小时前
VMware在Ubuntu22.04驱动Livox Mid360s
linux·c++·嵌入式硬件·自动驾驶