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;
};
问:Point 的 x 和 y 在 Circle 外部可以直接访问吗?Circle 的 center 和 radius 呢?为什么?
习题 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 的值是什么?