c++易错知识点(基于计算机挑战赛真题总结)
C++中运算符的优先级(从高到低):
- 括号、数组下标、成员访问等(如 () [] -> . )
- 单目运算符(如 ! ~ ++ – * & (类型) sizeof )
- 算术运算符(* / % 高于 + -)
- 移位运算符(<< >>)
- 关系运算符(< <= > >=)
- 相等运算符(== !=)
- 位运算符(& ^ |)
- 逻辑运算符(&& ||)–逻辑AND(&&)的优先级高于逻辑OR(||)
- 条件运算符(?:)
- 赋值运算符(= += 等)
- 逗号运算符(,)
在逻辑运算符中,||(逻辑或)和&&(逻辑与)具有短路行为。对于||,如果左边操作数为真,则右边不会计算。对于&&,如果左边操作数为假,则右边不会计算。
文件打开模式
- ios::in:以读取方式打开文件,如果文件不存在,则打开失败。
- ios::out:以写入方式打开文件,如果文件不存在,则创建文件;如果文件已存在,则默认清空文件内容(除非同时使用了其他模式如app)。
- ios::app:以追加模式打开文件,每次写入操作都在文件末尾进行。
- ios::ate:打开文件后立即定位到文件末尾。
- ios::trunc:如果文件已存在,则清空文件内容。注意,如果同时指定了ios::out且没有指定ios::app、ios::ate或ios::in,则默认使用ios::trunc。
- ios::binary:以二进制模式打开文件。
注意:这些模式可以组合使用,例如ios::in | ios::out 表示以读写方式打开文件。
下面是一些常见的组合及其行为:
- ios::in | ios::out:以读写方式打开文件,文件必须存在,否则打开失败。
- ios::in | ios::out | ios::trunc:以读写方式打开文件,如果文件不存在则创建,如果存在则清空。
- ios::out | ios::trunc:等同于ios::out,因为ios::out默认会清空文件。
- ios::app | ios::out:以追加方式写入,文件不存在则创建,存在则追加。
- ios::in | ios::binary:以二进制模式读取文件
基本原则:
- 如果模式中包含ios::out且不包含ios::app,则默认会清空文件,除非同时包含了ios::in(在这种情况下,有些编译器不会清空,但标准并未明确,因此最好避免依赖这种行为)。
- ios::app模式会保证所有写入都在文件末尾,并且不会清空文件。如果同时指定了ios::app和ios::out,则不会清空文件。
- ios::ate只是在打开文件后定位到末尾,它本身不改变文件的清空行为。清空行为由ios::out和ios::trunc控制。
- 如果模式中包含ios::in且不包含ios::out,则文件必须存在,否则打开失败。
- 如果模式中包含ios::out且不包含ios::in,则文件不存在时会创建文件,文件存在时默认清空文件(除非有ios::app或ios::ate?注意:ios::ate不会阻止清空,而ios::app会
文件操作的状态标识符
状态标识符
-
goodbit:无错误,流处于正常状态。
-
eofbit:到达文件末尾。
-
failbit:一个输入操作未能读取预期的字符,或者一个输出操作未能写入预期的字符(例如,尝试读取一个不存在的文件,或者写入一个只读文件)。通常,failbit表示流仍然可以使用,但某个操作失败了。
-
badbit:流出现严重错误,可能无法继续使用(例如,在读取过程中出现无法恢复的I/O错误)。
验证状态的方法:
C++流对象(如ifstream, ofstream, fstream)提供以下成员函数来检查状态:
• good():如果流处于正常状态(没有错误标志被设置),返回true。
• eof():如果设置了eofbit,返回true。
• fail():如果设置了failbit或badbit,返回true。
• bad():如果设置了badbit,返回true。
• rdstate():返回当前流的状态,即一个包含当前设置的状态位的值。
此外,我们还可以使用clear()函数来重置流的状态。
循环队列的基本概念
假设我们有一个循环队列,数组大小为 n+1(这样才能存储 n 个元
为什么需要区分空和满?
如果只用 front == rear 来判断,无法区分队列是空还是满:
• 空队列:front == rear
• 满队列:如果也让 front == rear,就无法区分
解决方案:牺牲一个存储单元
我们让队列最多只使用 n 个存储单元(数组大小为 n+1),这样:
• 队列空:front == rear
• 队列满:(rear + 1) % (n+1) == front
函数重载的区分依据
在 C++ 中,函数重载是通过函数的参数列表来区分的,而不是通过返回值类型。
正确的重载条件
函数重载允许在同一作用域内定义多个同名函数,只要它们的参数列表不同:
- 参数类型不同
- 参数个数不同
- 参数顺序
string 类的 capacity() 成员函数的功能是:
功能定义
capacity() 返回当前字符串对象为其字符分配的存储空间的总容量,即在不重新分配内存的情况下,字符串最多可以容纳的字符数。
与相关函数的区别
| 函数 | 返回值 | 含义 |
|---|---|---|
size() / length() |
size_t |
字符串当前实际包含的字符数 |
capacity() |
size_t |
当前已分配的内存可容纳的字符总数 |
max_size() |
size_t |
字符串理论上可达到的最大长度 |
容量自动增长
当字符串长度超过当前容量时,系统会自动重新分配更大的内存块:
C++流
在C++中,流(stream)是用于处理输入输出的重要机制。以下是对C++流的正确描述:
正确描述要点:
- 流是字节序列的流动,提供了数据在源和目的地之间的传输通道。
- 标准流对象:
◦ cin:标准输入流对象(istream类)
◦ cout:标准输出流对象(ostream类)
◦ cerr:标准错误流(无缓冲)
◦ clog:标准日志流(有缓冲) - 类型安全:C++流提供类型安全的输入输出操作。
- 需要包含头文件:
◦ 使用标准流需要包含
◦ 文件流需要
◦ 字符串流需要 - 流状态检查:可以通过 good()、fail()、eof()、bad() 等成员函数查询流状态。
- 流对象不可复制:C++流对象不能被复制(复制构造函数和赋值运算符被禁用)。
常见错误描述:
• ❌ “流操作符 << 和 >> 只能用于位运算” → 错误,它们被重载用于流操作
• ❌ “流只能用于控制台输入输出” → 错误,流可用于文件、字符串等
• ❌ “流操作不是类型安全的” → 错误,C++流是类型安全的
非模板函数和模板函数的主要区别:
- 定义方式
// 模板函数 - 通用,可处理多种类型
template <typename T>
T Max(T a, T b)
{
return a > b ? a : b;
}
// 非模板函数 - 特定类型
int Max(int a, int b)
{
return a > b ? a : b;
}

- 类型处理
• 模板函数:类型参数化,编译器根据调用时的实参类型自动实例化
• 非模板函数:类型固定,只处理声明时指定的类型
this指针,友元函数,纯虚函数
1. this 指针的本质
this指针是成员函数的隐含参数- 编译器自动为每个非静态成员函数添加
this参数 this指向调用该成员函数的对象
2. 友元函数的性质
class Student {
private:
int score;
public:
Student(int s = 0) : score(s) {}
// 友元函数声明
friend void display(const Student& s);
};
// 友元函数定义 - 普通函数,不是成员函数
void display(const Student& s) {
cout << s.score << endl; // 通过参数访问,没有this
// 这里不能使用 this->score,因为不是成员函数
}
3. 对比成员函数和友元函数
| 特性 | 成员函数 | 友元函数 |
|---|---|---|
| this 指针 | 有 | 无 |
| 调用方式 | obj.func() |
func(obj) |
| 访问权限 | 自动有访问权限 | 需要显式声明为friend |
| 所属关系 | 类的成员 | 独立的外部函数 |
友元函数可以访问类的 private 和 protected 成员。
类的友元函数是定义在类外部,但有权访问类的所有私有(private)成员和保护(protected)成员。尽管友元函数的原型有在类的定义中出现过,但是友元函数并不是成员函数。
友元可以是一个函数,该函数被称为友元函数;友元也可以是一个类,该类被称为友元类,在这种情况下,整个类及其所有成员都是友元。
如果要声明函数为一个类的友元,需要在类定义中该函数原型前使用关键字 friend
不能继承
友元关系是单向的
纯虚函数(Pure Virtual Functions):
- 一个包含纯虚函数的类被称为抽象类(Abstract Class),它不能被直接实例化。
- 纯虚函数没有函数体,声明时使用
= 0。 - 它强制派生类提供具体的实现。
抽象类
抽象类可以包含:
- 纯虚函数(必须至少有一个)
- 普通成员函数
- 数据成员
- 构造函数和析构函数
- 其他虚函数(非纯虚)
关键点:抽象类可以有完整的实现,只是不能实例化。它必须至少有一个纯虚函数,但可以包含其他任何类型的成员。
构造函数
常量成员(const 数据成员)必须在构造函数里初始化,否则编译报错。
拷贝构造函数是C++中的一种特殊构造函数,用于创建一个新对象作为已存在对象的副本。
- 基本语法
class MyClass {
public:
// 拷贝构造函数声明
MyClass(const MyClass& other);
};
- 特点
- 函数名:与类名相同
- 参数:对同类对象的const引用
- 返回类型:无(构造函数)
- 调用时机:对象拷贝时自动调用
- 拷贝构造函数是成员函数,可以访问类的所有 private 成员
- 访问控制基于类,同一类的成员函数可以访问任何同类对象的 private 成员
- 这是 C++ 语言设计的重要特性,确保类能够完整地控制自己的复制行为
访问原理
拷贝构造函数作为类的成员函数,可以访问:
- 当前对象的所有成员(通过
this指针) - 同类其他对象的所有成员(包括 private)
类的构造函数是一种特殊的函数,在创建一个新的对象时调用。类的析构函数也是一种特殊的函数,在删除所创建的对象时调用。
析构函数(Destructor)
析构函数是C++中的一种特殊成员函数,用于在对象销毁时执行清理工作。
1. 基本语法
cpp
class MyClass {
public:
~MyClass(); // 析构函数声明
};
2. 特点
- 函数名:
~+ 类名 - 参数:无参数
- 返回类型:无
- 调用时机:对象销毁时自动调用
- 不能被重载:一个类只能有一个析构函数
3. 调用时机
析构函数在以下情况自动调用:
局部对象离开作用域;
动态对象被delete:
对象数组被销毁:
程序结束:
如果用户没有定义析构函数,编译器会自动生成一个
当类包含动态分配的资源时,必须自定义析构函数
对于栈对象,手动调用后仍然会被自动再次调用
4.析构顺序
单个对象:
- 执行析构函数体
- 销毁成员对象(按声明逆序)
- 调用基类析构函数(如果有)
多个对象:
- 局部变量:后创建的先销毁(栈的特性)
- 成员对象:按声明顺序的逆序销毁
- 继承关系:派生类先析构,基类后析构
注意事项
- 不能显式调用:析构函数由系统自动调用
- 不能重载:每个类只有一个析构函数
- 异常安全:析构函数不应抛出异常
- 虚析构函数:基类应该使用虚析构函数
- 资源管理:确保释放所有分配的资源
- 无法继承
构造函数(Constructor)
构造函数是C++中的一种特殊成员函数,用于在创建对象时初始化对象。
1. 基本语法
cpp
class MyClass {
public:
MyClass(); // 构造函数声明
};
2. 特点
- 函数名:与类名完全相同
- 返回类型:无(不写void)
- 调用时机:对象创建时自动调用
- 可以重载:一个类可以有多个构造函数
关于调用:
- 引用只是别名,不创建新对象,因此不调用任何构造函数
- 值传递、值返回、对象初始化都会创建新对象,因此可能调用拷贝构造函数
一个派生类继承了所有的基类方法,但下列情况除外:
- 基类的构造函数、析构函数和拷贝构造函数。
- 基类的重载运算符。
- 基类的友元函数。
访问说明符
在C++中,类的成员访问属性由访问说明符决定。访问说明符包括:private、protected、public。
规则:
- 如果没有指定访问说明符,在类中定义的成员默认为private(在class中,默认访问权限是private)。
- 访问说明符的作用从出现处开始,直到下一个访问说明符或类结束。
C++ 多态的展现形式
C++ 中的多态主要有两种形式:编译时多态和运行时多态。
1. 编译时多态(静态多态)
(1) 函数重载
(2) 运算符重载
(3) 模板
2. 运行时多态(动态多态)
(1) 虚函数
运行期多态是基于虚函数和继承实现的
对于运行期多态,虚函数在运行期在确定,所以编译器无法对虚函数进行优化
3. 多态的实现机制
虚函数表(vtable)
编译期多态可以基于函数的重载解析
核心原则:派生类包含基类,但基类不包含派生类。-------多态的基础
动态联编
是C++中实现多态性的一种机制。它在程序运行时根据对象的实际类型来确定调用哪个函数,而不是在编译时确定。动态联编主要通过虚函数和继承实现,允许基类指针或引用在运行时绑定到派生类对象的函数。
关键特点:
- 运行时决策:函数调用在运行时解析,依赖于对象的实际类型。
- 基于虚函数:使用
virtual关键字声明虚函数,派生类可以重写这些函数。 - 实现多态:允许同一接口在不同派生类中有不同实现。
静态联编
在编译时确定函数或运算符的调用。函数重载和运算符重载都属于静态联编,因为编译器在编译阶段根据参数类型或运算符上下文直接解析到具体的函数。
判断两个函数是否可以重载的方法
在C++中,判断两个函数是否可以重载主要依据函数签名。函数签名包括函数名和参数列表。
1. 重载的核心规则
两个函数可以重载的条件:
- 函数名相同
- 参数列表不同(参数类型、个数、顺序至少有一个不同)
- 没有参数的两个函数是不能重载的
判断流程
判断步骤:
- 检查函数名是否相同
- 检查参数列表是否真正不同:
- 参数类型不同 ✓
- 参数个数不同 ✓
- 参数顺序不同 ✓
- 底层const不同 ✓
- 排除无效区别:
- 仅返回类型不同 ❌
- 仅参数名不同 ❌
- 仅顶层const不同 ❌
new 运算符的工作流程
当使用 new 创建对象时,会发生以下步骤:
- 分配内存 - 在堆上分配足够的内存
- 调用构造函数 - 在分配的内存上调用构造函数初始化对象
- 返回指针 - 返回指向新创建对象的指针
调用函数类型判断
-
看对象创建语法
-
无参数 → 默认构造
-
有参数 → 参数化构造
-
用对象初始化 → 拷贝构造
当我们用一个已存在的对象(左值)来初始化另一个对象时,会调用拷贝构造函数。
-
用右值初始化 → 移动构造
当我们用一个右值(例如临时对象、std::move转换后的对象)来初始化另一个对象时,会调用移动构造函数(如果存在的话)
-
-
看参数匹配
- 编译器选择最匹配的参数列表
-
看上下文
- 函数参数 → 可能拷贝构造
- 函数返回 → 可能移动构造
- 容器操作 → 可能各种构造
静态 vs 非静态成员函数对比
| 特性 | 非静态成员函数 | 静态成员函数 |
|---|---|---|
| this指针 | 有 | 无 |
| 访问非静态数据 | ✅ 可以 | ❌ 不可以 |
| 访问非静态函数 | ✅ 可以 | ❌ 不可以 |
| 访问静态数据 | ✅ 可以 | ✅ 可以 |
| 调用静态函数 | ✅ 可以 | ✅ 可以 |
| 调用方式 | 通过对象调用 | 通过类名或对象调用 |
所有成员函数(包括构造/析构)都可以访问静态成员
C++ 静态成员变量
核心:静态成员属于类,被所有对象共享。非静态成员是对象的独有属性。
基本概念
- 属于类,不属于任何对象
- 所有对象共享同一份数据
- 不占用对象内存空间
- 程序生命周期内一直存在
关键规则
- 类内声明:加
static关键字 - 类外定义:必须单独定义(除整型常量)
- 共享性:所有对象修改的是同一个变量
- 访问控制:受 public/private/protected 限制
不可重载的运算符
. .* :: ?: sizeof typeid alignof noexcept
c++继承
关键特性
- 代码复用:派生类复用基类代码
- 扩展功能:派生类可以添加新功能
- 多态支持:通过虚函数实现运行时多态
- 访问控制:不同继承方式影响成员可见性
核心:继承实现"是一个"关系,支持代码复用和多态。
访问控制和继承
派生类可以访问基类中所有的非私有成员。因此基类成员如果不想被派生类的成员函数访问,则应在基类中声明为 private。
我们可以根据访问权限总结出不同的访问类型,如下所示:
| 访问 | public | protected | private |
|---|---|---|---|
| 同一个类 | yes | yes | yes |
| 派生类 | yes | yes | no |
| 外部的类 | yes | no | no |
一个派生类继承了所有的基类方法,但下列情况除外:
- 基类的构造函数、析构函数和拷贝构造函数。
- 基类的重载运算符。
- 基类的友元函数。、
当使用不同类型的继承时,遵循以下几个规则:
- 公有继承(public):当一个类派生自公有基类时,基类的公有成员也是派生类的公有成员,基类的保护成员也是派生类的保护成员,基类的私有成员不能直接被派生类访问,但是可以通过调用基类的公有和保护成员来访问。
- 保护继承(protected): 当一个类派生自保护基类时,基类的公有和保护成员将成为派生类的保护成员。
- 私有继承(private):当一个类派生自私有基类时,基类的公有和保护成员将成为派生类的私有成员。
多重继承
- 多重继承中不同父类可以有同名成员(使用作用域解析)
- 多重继承可以使用不同的继承方式
- 多重继承的类可以继续被继承
int fun()和int fun(void)
在C和C++中,int fun()和int fun(void)之间有着微妙但重要的区别:
int fun(): 这种函数声明表示fun函数可以接受任意数量的参数,但是如果没有参数传递给它,它会假定参数类型。这种声明在C++中并不是推荐的写法,因为它允许传递不确定数量的参数给函数。int fun(void): 这种函数声明明确指定了fun函数不接受任何参数。在C和C++中,使用void作为参数列表表示没有参数。这种声明表明函数不会接受任何参数,并且在调用时不能传递任何参数给它。
类的实例化
是指根据类定义创建具体对象的过程。类本身不占用内存空间;只有当类被实例化后,才会分配内存并初始化对象,从而可以使用类的成员变量和成员函数。
关键点:
- 实例化触发构造函数:当创建对象时,会自动调用类的构造函数进行初始化。
- 内存分配:实例化时,系统为对象分配内存(包括成员变量)。
- 多个对象:一个类可以实例化多个对象,每个对象有独立的内存空间。
阻止实例化:
方法1:将构造函数声明为私有(private)
方法2:使用纯虚函数(但这会使得类成为抽象类,且必须通过派生类才能使用,而且派生类必须实现纯虚函数)
更多推荐

所有评论(0)