
C/C++ 不牢知识点汇总
一、指针数组和数组指针的区别
例如 int(*p)[5]
和 int *p[5]
是两种不同的声明方式,它们在语法和语义上有着显著的区别。
int(*p)[5]
解释: 这是一个指向包含5个整数的数组的指针。
详细解释:
p
是一个指针,它指向一个包含5个整数的数组。换句话说,p
可以指向任何具有5个整数的数组。示例:
int arr[5] = {1, 2, 3, 4, 5};
int (*p)[5] = &arr; // p 指向 arr 数组
int *p[5]
解释: 这是一个包含5个指向整数的指针的数组。
详细解释:
p
是一个数组,这个数组有5个元素,每个元素都是一个指向整数的指针。换句话说,p
是一个指针数组,其中每个元素都可以指向一个整数。示例:
int a = 1, b = 2, c = 3, d = 4, e = 5;
int *p[5] = {&a, &b, &c, &d, &e}; // p 是一个指针数组,每个元素指向一个整数
总结
int(*p)[5]
是一个指向包含5个整数的数组的指针。int *p[5]
是一个包含5个指向整数的指针的数组。
这两种声明方式在用途和应用场景上有很大的不同,理解它们的区别对于正确使用指针和数组至关重要。
二、纯虚函数的定义和说明
纯虚函数的定义
纯虚函数是在基类中声明但不定义的虚函数,表示该函数没有具体实现,而且强制派生类必须实现这个函数。
纯虚函数的语法是在函数声明后加 = 0
,例如:
virtual void func() = 0;
包含纯虚函数的类称为抽象类,抽象类不能直接实例化。
举例:动物类和派生类
假设我们有一个“动物”的基类,基类提供一个虚函数 makeSound()
,表示动物发出的声音。但因为每种动物发出的声音不同,所以我们不在基类中定义这个函数,而是让派生类(比如狗、猫)去具体实现。
代码示例:
#include <iostream>
using namespace std;
// 抽象类:Animal(基类)
class Animal {
public:
// 纯虚函数
virtual void makeSound() = 0;
// 普通成员函数
void eat() {
cout << "This animal is eating." << endl;
}
};
// 派生类:Dog
class Dog : public Animal {
public:
// 实现纯虚函数
void makeSound() override {
cout << "Woof! Woof!" << endl; // 狗的叫声
}
};
// 派生类:Cat
class Cat : public Animal {
public:
// 实现纯虚函数
void makeSound() override {
cout << "Meow! Meow!" << endl; // 猫的叫声
}
};
int main() {
// Animal a; // 错误!抽象类不能实例化
// 创建 Dog 和 Cat 对象
Dog dog;
Cat cat;
// 调用纯虚函数的实现
dog.makeSound(); // 输出:Woof! Woof!
cat.makeSound(); // 输出:Meow! Meow!
// 调用基类的普通函数
dog.eat(); // 输出:This animal is eating.
cat.eat(); // 输出:This animal is eating.
return 0;
}
关键点解析
纯虚函数定义:
在基类Animal
中定义了一个纯虚函数:
virtual void makeSound() = 0;
它没有实现,表示“每种动物发出的声音由具体的动物类决定”。
抽象类不能实例化:
因为Animal
类包含纯虚函数,所以它是一个抽象类,无法直接创建对象:
Animal a; // 错误
派生类必须实现纯虚函数:
在派生类Dog
和Cat
中,必须实现makeSound()
函数,否则派生类本身也会成为抽象类。多态性(可扩展性):
如果将来需要增加其他动物(比如鸟类),只需要继承Animal
类并实现makeSound()
,不需要修改基类的代码。
纯虚函数的应用场景
纯虚函数主要用于以下情况:
定义通用接口:
抽象类定义了派生类必须实现的接口,但不提供具体实现,强制派生类实现特定功能。多态设计:
通过基类指针或引用调用派生类的实现(即运行时多态)。框架设计:
纯虚函数在面向对象的框架设计中非常常见,用于提供可扩展的功能。
三、类模版
类模板允许用户为类定义一种通用模式,使得类中的成员变量或成员函数的类型可以是任意类型(通常由用户在实例化时指定)。
通过使用类模板,可以创建更加通用、灵活的类,而无需为不同类型重复编写代码。
类模板的语法:
template <typename T> // 声明模板,T 是类型参数
class ClassName {
T data; // T 是占位符,用于表示类型
public:
ClassName(T value) : data(value) {}
T getData() { return data; }
};
T
是一个模板类型参数,可以是任意类型(如int
,double
,string
等)。在使用时,需要根据具体的类型来实例化模板类。
示例代码:实现一个类模板
#include <iostream>
using namespace std;
// 定义类模板
template <typename T>
class MyClass {
T data; // 数据成员类型为模板类型 T
public:
MyClass(T value) : data(value) {} // 构造函数
T getData() { return data; } // 返回数据成员的值
};
int main() {
MyClass<int> intObj(10); // 实例化模板类为 int 类型
MyClass<double> doubleObj(3.14); // 实例化模板类为 double 类型
MyClass<string> stringObj("Hello"); // 实例化模板类为 string 类型
cout << "intObj: " << intObj.getData() << endl; // intObj: 10
cout << "doubleObj: " << doubleObj.getData() << endl; // doubleObj: 3.14
cout << "stringObj: " << stringObj.getData() << endl; // stringObj: Hello
return 0;
}
四、析构函数
析构函数的定义
析构函数(Destructor) 是 C++ 类中的一种特殊成员函数,在对象的生命周期结束时自动被调用,用来执行清理工作,比如释放资源(内存、文件句柄等)。
析构函数的特点
1. 函数名称与类名相同,但在前面加上一个 波浪号(
~
),例如:
~ClassName();
2. 没有返回值(包括
void
),也不能有参数,因此不能被重载。3. 作用:
释放对象占用的资源(如动态分配的内存、打开的文件、网络连接等)。
执行清理操作,保证对象销毁时的状态一致性。
4. 自动调用:
当对象生命周期结束时,析构函数会被自动调用(如对象超出作用域或被显式删除)。
析构函数的作用
析构函数通常用于:
释放动态分配的内存: 如果对象内部使用了
new
动态分配内存,析构函数应负责调用delete
。关闭文件或网络连接: 如果对象打开了文件或网络连接,析构函数可用来关闭它们。
清理临时资源: 比如释放锁、删除临时文件等。
析构函数的代码示例
示例 1:基本析构函数
#include <iostream>
using namespace std;
class MyClass {
public:
MyClass() {
cout << "Constructor called." << endl;
}
~MyClass() {
cout << "Destructor called." << endl;
}
};
int main() {
MyClass obj; // 创建对象时调用构造函数
// 对象超出作用域时(main 函数结束),自动调用析构函数
return 0;
}
输出结果:
Constructor called.
Destructor called.
解析:
对象
obj
被创建时,调用构造函数。程序运行到对象超出作用域时,自动调用析构函数。
示例 2:释放动态内存
#include <iostream>
using namespace std;
class MyClass {
private:
int* data; // 动态分配的内存
public:
// 构造函数:分配内存
MyClass() {
data = new int;
*data = 42; // 给动态分配的内存赋值
cout << "Constructor: Memory allocated." << endl;
}
// 析构函数:释放内存
~MyClass() {
delete data; // 释放动态分配的内存
cout << "Destructor: Memory released." << endl;
}
};
int main() {
MyClass obj; // 创建对象
return 0; // 程序结束时自动调用析构函数
}
复制代码
输出结果:
Constructor: Memory allocated.
Destructor: Memory released.
解析:
构造函数分配了动态内存(
new
),析构函数释放了内存(delete
)。析构函数保证程序结束时不会造成内存泄漏。
注意事项
不要在析构函数中显式调用析构函数:
析构函数会自动调用,显式调用会导致不可预期的行为(如递归调用)。虚析构函数:
当基类有析构函数且需要通过基类指针删除派生类对象时,析构函数应声明为虚函数,以确保正确调用派生类的析构函数。
virtual ~Base();
避免双重释放: 确保析构函数释放的资源没有被重复释放(如重复调用
delete
)。
总结
1. 析构函数是用来清理资源的特殊函数,在对象生命周期结束时自动调用。
2. 它保证了对象销毁时的资源释放,是构造函数的“对称”操作。
3. 如果你的类中使用了动态内存分配或其他需要清理的资源,那么实现一个析构函数是非常重要的!
五、继承的三种方式及访问权限的变化
public继承:
基类的
public
成员在派生类中仍是public
。基类的
protected
成员在派生类中仍是protected
。基类的
private
成员不可被派生类直接访问,但存在于派生类对象中(可以通过基类的public
或protected
成员间接访问)。
protected继承:
基类的
public
成员在派生类中变为protected
。基类的
protected
成员在派生类中仍是protected
。基类的
private
成员不可被派生类直接访问。
private继承:
基类的
public
成员在派生类中变为private
。基类的
protected
成员在派生类中变为private
。基类的
private
成员不可被派生类直接访问。
- 感谢你赐予我前进的力量