1. 从结构体到类
详细概念:
- 在 C++ 中,
struct
和class
都用于定义数据结构。struct
主要用于表示简单数据结构,而class
则用于封装复杂的数据和行为。 struct
和class
之间的主要区别在于默认访问权限:struct
的成员默认是public
,而class
的成员默认是private
。- 使用
struct
可以更方便地实现简单的数据结构,而class
提供了更多的封装、继承和多态特性。 - 在 C++ 中,可以使用
typedef
或using
来简化类型定义。 -
#include <iostream>
struct MyStruct {
int x; // 默认 public
};class MyClass {
private: // 默认 private
int x;public:
MyClass(int val) : x(val) {} // 构造函数int getX() const { return x; } // 访问器函数
};int main() {
MyStruct s;
s.x = 10; // 直接访问 public 成员
std::cout << "MyStruct x: " << s.x << std::endl;MyClass c(20); // 创建 MyClass 对象
std::cout << "MyClass x: " << c.getX() << std::endl; // 通过访问器函数获取值return 0;
} -
概念补充:
struct
和class
的使用场景不同:struct
通常用于简单数据存储,而class
更适合复杂的数据结构和行为。struct
适合轻量级对象,不需要复杂逻辑时使用。 - 对比:
struct
:默认public
,适合简单数据。class
:默认private
,适合复杂逻辑和封装。 - 实际应用:使用
struct
设计数据传输对象(DTO),如网络传输中的 JSON 数据映射。使用class
设计业务逻辑层对象,管理复杂的行为和状态。
2. 类的访问权限
C++ 中类的访问权限有三种:
public
:公开访问protected
:保护访问,仅限于该类及其派生类private
:私有访问,仅限于该类内部- 访问权限控制了外部代码对类成员的访问。正确使用访问权限有助于实现封装,保护数据不被外部代码随意修改。
-
实际应用:在设计 API 时,将重要的实现细节设为
private
,公开接口方法为public
。设计基类时,保护成员变量以避免直接修改。 -
#include <iostream>
class MyClass {
public:
int publicVar; // 任何地方可访问protected:
int protectedVar; // 仅限派生类访问private:
int privateVar; // 仅限本类访问public:
MyClass(int pub, int prot, int priv)
: publicVar(pub), protectedVar(prot), privateVar(priv) {}void display() {
std::cout << "Public: " << publicVar << ", Protected: " << protectedVar
<< ", Private: " << privateVar << std::endl;
}
};class Derived : public MyClass {
public:
Derived(int pub, int prot, int priv) : MyClass(pub, prot, priv) {}void show() {
std::cout << "Derived Public: " << publicVar << ", Protected: " << protectedVar << std::endl;
// std::cout << "Private: " << privateVar; // 错误,无法访问私有成员
}
};int main() {
MyClass obj(1, 2, 3);
obj.display(); // 访问公共和保护成员Derived d(4, 5, 6);
d.show(); // 访问派生类中的公共和保护成员return 0;
} -
补充知识点:
- 访问权限的正确使用能提升代码的安全性和可维护性。
- 可以使用
friend
关键字让特定函数或类访问私有成员。
3. 简单使用类
类结合数据和行为,允许对象通过成员函数进行数据操作。成员函数可以修改对象的状态,并提供对外接口
#include <iostream>
class Rectangle {
private:
int width, height; // 封装数据
public:
// 构造函数
Rectangle(int w, int h) : width(w), height(h) {}
// 成员函数:计算面积
int area() const { // const 修饰,表示该函数不会修改对象
return width * height;
}
// 设置尺寸
void setDimensions(int w, int h) {
width = w;
height = h;
}
void display() const { // 显示尺寸
std::cout << "Width: " << width << ", Height: " << height << std::endl;
}
};
int main() {
Rectangle rect(5, 10); // 创建对象并初始化
rect.display(); // 输出 Width: 5, Height: 10
std::cout << "Area: " << rect.area() << std::endl; // 输出面积
return 0;
}
补充知识点:
- 通过成员函数实现封装,外部代码无法直接访问对象的私有数据。
- 使用
const
关键字可以防止成员函数修改对象状态,增加代码的可读性和安全性。
4. 构造函数与析构函数
详细概念:
- 构造函数用于初始化对象,可以重载以接受不同数量和类型的参数。
- 析构函数用于清理资源,比如释放动态分配的内存。它没有参数和返回值。
代码示例:
#include <iostream>
class MyClass {
public:
MyClass() { // 默认构造函数
std::cout << "Default Constructor called!" << std::endl;
}
MyClass(int value) { // 带参数的构造函数
std::cout << "Parameterized Constructor called with value: " << value << std::endl;
}
~MyClass() { // 析构函数
std::cout << "Destructor called!" << std::endl;
}
};
int main() {
MyClass obj1; // 调用默认构造函数
MyClass obj2(10); // 调用带参数的构造函数
// obj1 和 obj2 的生命周期结束时调用析构函数
return 0;
}
补充知识点:
- 如果没有定义构造函数,C++ 会生成一个默认构造函数。
- 如果类中有指针成员,建议在析构函数中释放动态分配的内存,以防内存泄漏。
5. 构造函数的细节
详细概念:
- 初始化列表是一种更高效的初始化方式,特别适用于 const 和引用类型的成员。
- 可以在同一个类中定义多个构造函数(重载)。
代码示例:
#include <iostream>
class Point {
public:
int x, y;
// 默认构造函数
Point() : x(0), y(0) {
std::cout << "Default Point created at (0, 0)" << std::endl;
}
// 带参数的构造函数
Point(int xVal, int yVal) : x(xVal), y(yVal) {
std::cout << "Point created at (" << x << ", " << y << ")" << std::endl;
}
void display() const {
std::cout << "Point: (" << x << ", " << y << ")" << std::endl;
}
};
int main() {
Point p1; // 调用默认构造函数
Point p2(10, 20); // 调用带参数的构造函数
p1.display(); // 输出 Point: (0, 0)
p2.display(); // 输出 Point: (10, 20)
return 0;
}
6. 拷贝构造函数
详细概念:
- 拷贝构造函数用于创建对象的副本,默认情况下执行浅拷贝。需要手动实现深拷贝以防止内存泄漏或数据冲突。
代码示例:
#include <iostream>
class MyClass {
public:
int* data; // 动态分配内存
MyClass(int value) {
data = new int(value); // 分配内存
std::cout << "Constructor called, data: " << *data << std::endl;
}
// 拷贝构造函数
MyClass(const MyClass& other) {
data = new int(*other.data); // 深拷贝
std::cout << "Copy constructor called, data: " << *data << std::endl;
}
~MyClass() {
delete data; // 释放内存
std::cout << "Destructor called!" << std::endl;
}
};
int main() {
MyClass obj1(42); // 创建对象
MyClass obj2 = obj1; // 使用拷贝构造函数创建副本
std::cout << "obj2 data: " << *obj2.data << std::endl; // 输出 42
return 0;
}
7.深拷贝与浅拷贝
详细概念:
- 浅拷贝:简单复制对象的所有字段,包括指针。会导致多个对象共享同一内存。
- 深拷贝:为每个对象分配新的内存,复制指针指向的数据,确保每个对象拥有独立的资源。
代码示例:
#include <iostream>
class Shallow {
public:
int* data;
Shallow(int value) {
data = new int(value); // 动态分配内存
}
// 浅拷贝构造函数
Shallow(const Shallow& other) : data(other.data) {} // 仅复制指针
~Shallow() {
delete data; // 可能导致错误
std::cout << "Destructor called!" << std::endl;
}
};
int main() {
Shallow obj1(42);
Shallow obj2 = obj1; // 浅拷贝,obj1 和 obj2 共享同一内存
std::cout << "obj2 data: " << *obj2.data << std::endl; // 输出 42
return 0; // 当程序结束时,析构函数被调用,会导致 double free 错误
}
补充知识点:
- 浅拷贝可能会导致程序崩溃或未定义行为,尤其是在多个对象共享指向同一内存的情况下。
- 深拷贝确保每个对象有独立的内存,避免资源冲突,适用于动态分配的资源。
8. 初始化列表
详细概念:
- 初始化列表允许在构造函数中直接初始化成员变量,特别是对于
const
和引用类型成员,必须使用初始化列表。
代码示例:
#include <iostream>
class MyClass {
public:
const int value; // const 成员
MyClass(int val) : value(val) {} // 使用初始化列表
void display() const {
std::cout << "Value: " << value << std::endl;
}
};
int main() {
MyClass obj(42);
obj.display(); // 输出 Value: 42
return 0;
}
补充知识点:
- 成员按声明顺序初始化,而非初始化列表的顺序。
- 使用初始化列表可以提高效率,特别是对于复杂对象的构造。
9. const 修饰成员函数
详细概念:
const
修饰符用于标识一个成员函数不会修改对象的状态。它提高了代码的安全性,允许 const 对象调用这些函数。- 非
const
函数可以修改对象,而const
函数不允许。
代码示例:
#include <iostream>
class MyClass {
public:
int value;
MyClass(int val) : value(val) {}
int getValue() const { // const 成员函数
return value; // 只读
}
void setValue(int val) { // 非 const 成员函数
value = val; // 可以修改
}
};
int main() {
MyClass obj(42);
std::cout << "Value: " << obj.getValue() << std::endl; // 输出 Value: 42
obj.setValue(100);
std::cout << "New Value: " << obj.getValue() << std::endl; // 输出 New Value: 100
return 0;
}
10. this 指针
详细概念:
this
指针指向当前对象的实例,通常用于区分成员变量和参数名相同的情况。
代码示例:
#include <iostream>
class MyClass {
public:
int value;
MyClass(int value) {
this->value = value; // 使用 this 区分
}
void show() {
std::cout << "Value: " << this->value << std::endl; // 可以省略 this
}
};
int main() {
MyClass obj(42);
obj.show(); // 输出 Value: 42
return 0;
}
补充知识点:
this
在类的静态成员函数中不可用,因为静态成员与具体的对象无关。this
指针可以用于链式调用,以便返回对象本身。
11. 类的静态成员
详细概念:
- 静态成员属于类而不是特定的对象。所有对象共享同一静态成员,适合用于类级别的属性或计数。
代码示例:
#include <iostream>
class MyClass {
public:
static int count; // 静态成员
MyClass() {
count++;
}
};
// 静态成员的定义和初始化
int MyClass::count = 0;
int main() {
MyClass obj1;
MyClass obj2;
std::cout << "Count: " << MyClass::count << std::endl; // 输出 Count: 2
return 0;
}
补充知识点:
- 静态成员可以通过类名直接访问,且与类的实例无关。
- 静态成员的生命周期与程序相同,直到程序结束时才释放。
12. 简单对象模型
详细概念:
- C++ 支持值语义和引用语义。值语义涉及对象的直接复制,引用语义涉及对象的引用或指针,允许共享和修改同一对象。
代码示例:
#include <iostream>
class MyClass {
public:
int value;
MyClass(int val) : value(val) {}
};
void byValue(MyClass obj) { // 值语义,复制对象
obj.value += 10; // 不影响原对象
}
void byReference(MyClass& obj) { // 引用语义,直接修改对象
obj.value += 10; // 影响原对象
}
int main() {
MyClass a(10);
byValue(a); // 调用 byValue,不影响 a
std::cout << "After byValue: " << a.value << std::endl; // 输出 10
byReference(a); // 调用 byReference,影响 a
std::cout << "After byReference: " << a.value << std::endl; // 输出 20
return 0;
}
补充知识点:
- 值语义适合简单数据类型或小对象,避免内存开销。
- 引用语义适合大型对象,能显著提高性能和效率。
13. 友元
详细概念:
- 友元函数或类可以访问类的私有和保护成员。友元关系是单向的,不对称。
代码示例:
#include <iostream>
class MyClass {
private:
int value;
public:
MyClass(int val) : value(val) {}
friend void showValue(MyClass obj); // 友元函数声明
};
void showValue(MyClass obj) {
std::cout << "Value: " << obj.value << std::endl; // 访问私有成员
}
int main() {
MyClass obj(42);
showValue(obj); // 输出 Value: 42
return 0;
}