C++易混淆知识点
1.常量指针和指针常量
- const修饰指针 — 常量指针— 指向的值是常量
const int * p = &a;- 特点:指针的指向可以改,但是指针指向的值不可以改
- const修饰常量 — 指针常量— 指向是常量
int * const p = &a;- 特点:指针的指向不可以改,但是指针指向的值可以改
2.程序调试

3.数值和指针
#define _CRT_SECURE_NO_WARNINGS 1
#include<bits\stdc++.h>
using namespace std;
int main()
{
//数组名作用
//1、可以通过数组名统计整个数组占用空间的大小
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
cout << "整个数组占用空间:" << sizeof(arr) << endl;
cout << "整数类型占用内存为:" << sizeof(arr[0]) << endl;
cout << "数组中元素的个数为:" << sizeof(arr) / sizeof(int) << endl;
//2、可以通过数组名查看数组首地址
cout << "数组的首地址为:" << (void*)arr << endl;
cout << "数组中第一个元素的地址为:" << (void*)&arr[0] << endl;
cout << "数组中第二个元素的地址为:" << (void*)&arr[1] << endl;
//3、数组名是常量,不可以赋值
// arr = arr + 1; // 错误,数组名不可改变
//4、声明一个指针,指向数组的第一个元素
int* p = arr;
cout << "指针p的值(数组首地址):" << (void*)p << endl;//把指针当做一个地址值来处理,通常用于打印地址或者进行通用指针的操作
cout << "指针p所指向的第一个元素的值:" << *p << endl;//*p解引用,指向第一个元素的值
// 指针可以改变指向位置
p = p + 1; // 指向第二个元素
cout << "指针p加1后指向第二个元素,值为:" << *p << endl;
// 指针变量占用的大小
cout << "指针变量p占用空间:" << sizeof(p) << endl;
// 指针可以赋值
int b[5] = { 100, 200, 300, 400, 500 };
p = b;
cout << "指针p指向新数组b的第一个元素,值为:" << *p << endl;
char str[] = "hello";//sizeof(str) = 6
return 0;
}
4.C++定义常量的两种方式
| 方面 | #define 宏常量 |
const 修饰的变量 |
|---|---|---|
| 本质 | 预处理指令,文本替换 | 编译时常量,具有类型 |
| 类型检测 | 无类型,不进行类型检查 | 有类型,编译器会进行类型检查 |
| 调试时信息 | 不保留调试信息,宏展开后看不到原名称 | 有符号信息,可以在调试器中查看名称和值 |
| 作用域 | 没有作用域,宏在定义后到文件末尾有效 | 有作用域,遵循C/C++作用域规则 |
| 表达能力 | 只能实现简单文本替换,不能定义复杂表达式 | 可以定义复杂的表达式和常量对象 |
| 内存占用 | 无内存开销,直接替换 | 可能占用内存(尤其在C中,C++中编译器可能优化掉) |
| 安全性 | 易出错,宏替换可能导致意外行为 | 类型安全,减少错误 |
| 示例 | #define PI 3.14159 |
const double pi = 3.14159; |
5.分文件编写
- 创建.h后缀的头文件 写函数的声明 swap.h #include<iostream>
#ifndef SWAP_H
#define SWAP_H
// 交换两个int变量的值
void swap(int& a, int& b);
#endif // SWAP_H
- 创建.cpp后缀的源文件 写函数的定义
- 在源文件中包含头文件,#include ”swap.h”
#include "swap.h"
void swap(int& a, int& b)
{
int temp = a;
a = b;
b = temp;
}
- 其他源文件直接包含头文件即可使用 #include ”swap.h”
#include <iostream>
#include "swap.h"
int main()
{
int x = 5;
int y = 10;
std::cout << "交换前:x = " << x << ", y = " << y << std::endl;
swap(x, y);
std::cout << "交换后:x = " << x << ", y = " << y << std::endl;
system("pause");
return 0;
}
用g++编译g++ main.cpp swap.cpp -o swap_test
用cmake编译:先在源文件目录下写一个CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(my)
set(CMAKE_CXX_STANDARD 17)
add_executable(my main.cpp swap.cpp)
然后新建一个build目录,cd进去,执行cmake -G "MinGW Makefiles" <CMakeLists.txt 文件路径>
会生成makefile文件,然后cmake --build .
6.C++内存分区
C++将内存大方向划分为4个区域
- 代码区:存放函数体的二进制代码,由操作系统进行管理
- 全局区:存放已初始化的全局变量和静态变量以及常量
(
| BSS 段 | 存放未初始化的全局变量和静态变量 |
)
- 栈区:由编译器自动分配释放,存放函数的参数值,局部变量等
- 堆区:由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收
代码运行前
- C++中在程序运行前分为全局区和代码区
- 代码区特点是共享和只读,只存一份
- 全局区中存放全局变量、静态变量、常量
- 常量区中存放const修饰的全局变量 和 字符串变量
- 局部变量、const修饰的局部变量(局部常量)不在全局区中

代码运行后
栈区(Stack)
- 栈是一块由编译器自动管理的内存区域,用于存储函数调用的局部变量、函数参数、返回地址等。
- 栈具有 先进后出(LIFO, Last In First Out) 结构特点。
- 分配速度快,通常由 CPU 寄存器维护栈指针自动分配释放空间。
- 栈空间大小一般有限(几 MB 左右),超出栈空间会导致栈溢出(Stack Overflow)。
- 内存由操作系统自动分配和释放,不需要程序员干预。
堆区(Heap)
- 堆是程序运行时由程序员手动申请和释放的一块动态内存区域。
- 堆的大小仅受限于系统虚拟内存大小,通常比栈大得多。
- 堆内存的分配和释放由程序员负责(
new和delete,malloc和free)。 - 动态分配,适合存储大小或生命周期不确定的数据。
- 分配速度相对较慢,因为需要维护更复杂的数据结构(空闲链表等)。
7.new delete和malloc free
#define _CRT_SECURE_NO_WARNINGS 1
#include<bits\stdc++.h>
int main(){
// 分配内存,但是不自动调用构造函数(本例中为int,不涉及构造函数)
int* p = (int*)malloc(sizeof(int) * 1);
// 需要手动赋值,不会自动初始化
*p = 10;
// 释放内存
free(p);
// 分配数组
int* arr = (int*)malloc(sizeof(int) * 5);
// 释放数组内存
free(arr);
// 分配一个int,并初始化为10
int* p2 = new int(10);
// 使用完释放内存
delete p2;
// 分配一个整型数组,长度为5
int* arr2 = new int[5];
// 释放动态数组
delete[] arr2;
return 0;
}
| 特性 | new / delete |
malloc / free |
|---|---|---|
| 语言 | C++ 原生操作符 | C 标准库函数 |
| 头文件 | 无需包含头文件 | 需包含 <cstdlib> 或 <stdlib.h> |
| 内存分配 | 分配指定类型大小,自动调用构造函数 | 分配任意字节大小,不调用构造函数 |
| 内存释放 | 自动调用析构函数后释放 | 不调用析构函数,直接释放内存 |
| 返回类型 | 返回指定类型指针,无需强制类型转换 | 返回 void*,一般需要强制转换成相应类型 |
8.static关键字和静态成员
static 关键字的几种用法
1.1 在全局变量或函数前
将变量或函数的链接属性改为内部链接,只在当前文件可见,避免与其他文件中同名符号冲突。
1.2 在函数内部的变量前
- 该变量变为静态局部变量,生命周期从程序开始直到结束,但作用域仍限于函数内部。
- 变量只初始化一次,且保存其状态。
void func() {
static int counter = 0; // 只初始化 1 次
counter++;
std::cout << counter << std::endl;
}
调用两次func(),第二次输出的是2
1.3 在类成员变量或成员函数前
- 静态成员属于类本身,而不是某个对象实例。
- 静态成员变量被所有对象共享。
- 静态成员函数只能访问静态成员变量,不能访问非静态成员变量,因为调用时没有对象实例。
class MyClass {
public:
static int count; // 静态成员变量声明
static void printCount(); // 静态成员函数声明
};int MyClass::count = 0; // 静态成员变量定义,必须在类外初始化
void MyClass::printCount() {
std::cout << "Count = " << count << std::endl;
}
9.C++对象模型和this指针
C++ 对象模型描述的是C++中类对象在内存中是如何布局和组织的,涉及成员变量、成员函数、继承、多态等方面。
- 非静态成员变量:
- 每个类对象都会拥有一份非静态成员变量的内存空间。
- 成员变量在对象中是连续存储的,顺序通常按声明顺序排列(但编译器可适当调整对齐填充)。
- 静态成员变量:
- 静态成员不存储在对象的内存空间中,只有一份静态成员变量存储在类的静态内存区,所有对象共享。
- 成员函数:
- 成员函数的代码只存储一份,不随对象而重复。函数指针不会放在对象中。
this 是一个指向当前对象的隐式指针,类型为 ClassName*。成员函数内部自动包含 this 指针,指向调用该函数的对象。
- 通过
this指针,成员函数可以访问调用它的具体对象的成员变量和函数。 - 解决名字隐藏(当局部变量和成员变量同名时,区分成员变量需要用
this->)。 - 可用于返回对象自身的指针,支持链式调用。
- 不是所有函数都有
this指针,静态成员函数没有this指针,因为它们不属于某个对象。

这里返回类型必须是Person& 而不是Person,否者会执行拷贝函数
加&相当于:Person& p'= p2;创建一个对 p2 的引用, 任何对 p' 的更改都会影响到 p2
不加&相当于Person p' = p2,创建 p2 的一个副本,即 p' 是一个新的 Person 对象
10.const关键字和常函数常对象
const 修饰符用于修饰变量、对象或函数,表示不可修改的含义。
常函数:在成员函数后加 const,表示这个成员函数不会修改对象的任何非 mutable 成员变量。
常对象:用 const 修饰的对象,其成员变量不能被修改,只能调用常成员函数。
mutable 关键字允许常成员函数修改这个成员变量。
class MyClass {
mutable int cache; // 即使在 const 成员函数也可以修改
public:
int getCache() const {
return cache++;
}
};
11.友元
友元(friend)机制允许你打破类的封装,某些函数或类可以访问该类的私有(private)和受保护(protected)成员。
1. 友元函数(Friend Function)
class A {
private:
int x;
public:
A(int val) : x(val) {}
friend void showX(const A& a); // 友元函数声明
};void showX(const A& a) {
std::cout << "x = " << a.x << std::endl; // 访问私有成员
}
#include <iostream>
using namespace std;class MyNumber {
private:
int value;
public:
MyNumber(int v = 0) : value(v) {}// 重载 + 运算符(成员函数)
MyNumber operator+(const MyNumber& other) const {
return MyNumber(this->value + other.value);
}// 重载 = 运算符
MyNumber& operator=(const MyNumber& other) {
if (this != &other) {
this->value = other.value;
}
return *this;
}// 友元形式,重载 << 运算符(输出流)
friend ostream& operator<<(ostream& os, const MyNumber& num);int getValue() const { return value; }
};// 实现友元函数 << 运算符重载
ostream& operator<<(ostream& os, const MyNumber& num) {
os << num.value;
return os;
}int main() {
MyNumber a(10);
MyNumber b(20);
MyNumber c = a + b; // 使用重载的 +cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl;MyNumber d;
d = c; // 使用重载的 =
cout << "d = " << d << endl;return 0;
}
2. 友元类(Friend Class)
- 一个类可以声明另一个类为其友元类。
- 友元类的所有成员函数均可以访问该类的私有和受保护成员。
class A {
private:
int x = 10;
friend class B; // B 是 A 的友元类
};class B {
public:
void showA(const A& a) {
std::cout << "A::x = " << a.x << std::endl; // 访问 A 的私有成员
}
};
3. 友元成员函数(Friend Member Function)
只让某个类的特定成员函数成为友元。
class B;//先声明
class A {
private:
int x = 10;
public:
friend void B::printX(const A& a); // B类中的printX成员函数是A的友元
};class B {
public:
void printX(const A& a) {
std::cout << "A::x = " << a.x << std::endl;
}
};
12.模板函数
//template <typename T> 表示定义一个模板,其中 T 是模板参数,可以理解为占位符类型。
template <typename T>
返回值类型 函数名(参数列表){
// 函数体中可以使用类型T }
swapValues<int>(x, y); // 显示指定模板类型为int
//多个函数模版
template <typename T1, typename T2>
void printPair(const T1& a, const T2& b) {
cout << a << " and " << b << endl;
}
13.静态多态和动态多态
静态多态(Static Polymorphism)
- 在编译期确定调用哪个函数。
- 主要通过函数重载(Function Overloading)、运算符重载(Operator Overloading)和模板机制实现。
- 调用开销小,没有运行时额外负担。
动态多态(Dynamic Polymorphism)
- 运行时决定调用哪个函数。
- 通过**虚函数(virtual functions)**实现。
- 允许通过基类指针或引用调用派生类重写的方法。
- 实现了接口的动态绑定,是面向对象的经典多态实现。
- 需要额外内存空间(如虚函数表指针)和运行时开销(虚函数调用开销)。
- 每个有虚函数的类,编译器会创建一个虚函数表(vtable),里面存放该类所有虚函数的地址。
- 每个对象实例中隐藏一个虚函数表指针(vptr)(通常是对象的第一个成员),指向它所属类的虚函数表。
class Base {
public:
virtual void show() {
std::cout << "Base show" << std::endl;
}
virtual ~Base() {} // 虚析构函数确保正确析构
};class Derived : public Base {
public:
void show() override {
std::cout << "Derived show" << std::endl;
}
};int main() {
Base* p = new Derived;/*
p指向一个Derived对象,内存中该对象的vptr指向Derived类的 vtable。- 调用
p->show() 时,并不是简单调用编译时绑定的函数地址。- 而是先从对象中取得
vptr,然后通过vptr找到所属类的 vtable,访问 vtable 中对应 函数的地址。- 调用这个地址,即调用了
Derived::show,实现动态绑定。*/
p->show(); // 输出 "Derived show",动态绑定
delete p;//// 同时调用 Derived 和 Base 的析构函数//虚析构函数保证通过基类指针删除派生类对象时,能正确调用派生类析构函数,释放派生类特有的资源。不写虚析构会导致Derived的析构函数未被调用,导致内存泄漏
}
C++ 中没有虚构造函数
虚函数依赖于对象存在
- 虚函数机制依赖于对象的
vptr指针,而vptr是对象被构造出来后才会设置的。 - 在构造函数执行期间,对象还不完全成立,特别是基类构造函数执行时,派生类部分尚未构造,
vptr指向基类对应的虚函数表。 - 因此,构造函数不能多态调用派生类版本,也就是说不支持虚构造函数。
14.C++ 继承基础和虚继承
C++ 继承是面向对象编程的核心特性之一,它支持代码复用和多态。其中,菱形继承(Diamond Inheritance)是多继承中经典的问题,C++通过虚继承机制解决这个问题。
C++ 继承基础
- 子类(派生类)可以继承父类(基类)的成员变量和成员函数(具体访问权限受
public、protected、private控制)。 - 继承类型:
- 公有继承(public):基类的
public和protected成员分别变为派生类的public和protected成员。 - 保护继承(protected) :基类的
public和protected成员变为派生类protected。 - 私有继承(private):基类所有成员在派生类中都变为
private。
- 公有继承(public):基类的
- 支持单继承和多继承,多继承允许一个类从多个基类继承。
多继承中成员变量重名问题
class A {
public:
int a = 1;
};class B {
public:
int a = 2;
};class C {
public:
int a = 3;
};class D : public A, public B, public C {};
要明确告诉编译器你想访问哪个基类的变量,方式是通过加上类名限定符:
cout << d.A::a << endl; // 访问 A 的 a -> 输出 1
cout << d.B::a << endl; // 访问 B 的 a -> 输出 2
cout << d.C::a << endl; // 访问 C 的 a -> 输出 3
解决菱形继承:虚继承(Virtual Inheritance)
class B : virtual public A { ... };
class C : virtual public A { ... };
class D : public B, public C {};//虚继承保证 D 中只存在一份 A 的子对象。
#include <iostream>
using namespace std;class A {
public:
int a = 1;
};class B {
public:
int a = 2;
};class C {
public:
int a = 3;
};class D : public A, public B, public C {};
int main() {
D d;
cout << d.A::a << endl; // 1
cout << d.B::a << endl; // 2
cout << d.C::a << endl; // 3// cout << d.a << endl; // 编译错误:二义性
return 0;
}
15.对比vector、list和deque
vector
是可变大小的动态数组,可以采用下标对vector的元素进行访问,而且它的大小会被容器自动处理。初始时,vector 通过分配一定大小连续内存空间来存储元素,空间由分配器(allocator)管理。当插入元素超过当前容量时,vector 会进行扩容。
[start ------------- finish ---------- end_of_storage]
|------ 当前存储数据 ------| |---- 预留空间 ----|
注意,迭代器end()会获取最后一个元素的下一个位置。

- 空间不足时,
vector申请新的更大的一块连续内存,vs下capacity是按1.5倍增长的,g++是按2倍增长的,来减少频繁扩容带来的性能开销。 - 扩容时,原有元素会被拷贝或移动到新内存中(调用元素的拷贝构造或移动构造函数)。
- 扩容后,释放旧的内存块。
常用的算法:
reserse(n)函数改变容器的最大容量,resize(n)函数改变容器中的有效元素个数。
size():当前元素数量,capacity():当前已分配可用容量,empty():判空
#include <iostream>
#include <vector>int main() {
std::vector<int> v = {10, 20, 30, 40, 50};// 1. 元素访问
std::cout << "元素访问:" << std::endl;
std::cout << "v[0] = " << v[0] << std::endl; // 通过 operator[] 访问,不做范围检查
std::cout << "v.at(1) = " << v.at(1) << std::endl; // 通过 at() 访问,有范围检查std::cout << "遍历所有元素:" << std::endl;
for (size_t i = 0; i < v.size(); ++i) {
std::cout << v[i] << " ";
}// 2. 插入元素
std::cout << "插入元素:" << std::endl;
v.push_back(60); // 尾部插入
v.insert(v.begin() + 2, 25); // 在第三个位置插入值为 25 的元素// 打印插入后的元素
for (int x : v) {
std::cout << x << " ";
}// 3. 删除元素
std::cout << "删除元素:" << std::endl;
v.erase(v.begin() + 3); // 删除第四个元素
v.pop_back(); // 删除末尾元素// 打印删除后的元素
for (auto it = v.begin(); it != v.end(); ++it) {
std::cout << *it << " ";
}return 0;
}
vector的迭代器在底层实际上就是一个指针。迭代器失效就是指迭代器底层对应指针所指向的空间被销毁了,指向的是一块被释放的空间,如果继续使用已经失效的迭代器,程序可能会崩溃。
list
是一个双向链表容器,实现了链式存储结构。不像 vector,它的元素不是连续存放的,而是通过节点指针相互连接。
- 优点是插入和删除任意位置元素都非常高效,时间复杂度为 O(1),不需要像
vector那样移动元素。 - 缺点是不支持随机访问,访问元素只能通过迭代器顺序遍历,时间复杂度为 O(n)。
- 相比
vector的连续存储,list的底层并没有一块连续内存,而是通过节点结构体链接实现。
#include <iostream>
#include <list>int main() {
std::list<int> lst = {10, 20, 30, 40, 50};// 1. 访问元素(只能顺序访问,不能通过索引)
std::cout << "list元素遍历: ";
for (auto it = lst.begin(); it != lst.end(); ++it) {
std::cout << *it << " ";
}// 2. 插入元素
lst.push_back(60); // 末尾插入
lst.push_front(5); // 头部插入auto it = lst.begin();
std::advance(it, 3); // 将迭代器移动到第四个元素
lst.insert(it, 25); // 在第四个位置插入25std::cout << "插入元素后:";
for (auto x : lst) {
std::cout << x << " ";
}// 3. 删除元素
lst.pop_back(); // 删除末尾元素
lst.pop_front(); // 删除头部元素it = lst.begin();
std::advance(it, 2);
lst.erase(it); // 删除第三个元素std::cout << "删除元素后:";
for (auto x : lst) {
std::cout << x << " ";
}// 4. 其他操作
std::cout << "list大小: " << lst.size() << std::endl;
std::cout << "list是否为空: " << (lst.empty() ? "是" : "否") << std::endl;lst.clear(); // 清空所有元素
std::cout << "清空后是否为空: " << (lst.empty() ? "是" : "否") << std::endl;return 0;
/* void splice (iterator position, list& x);
position:目标容器中的插入位置。x:待“拼接”的源列表,拼接后其元素会从x中被移除到目标列表。*/
}
在list中进行插入时是不会导致list的迭代器失效的,只有在删除时才会失效,并且失效的只是指向被删除节点的迭代器,其他迭代器不会受到影响。
deque
是双端队列(Double-Ended Queue)的简称。
- 支持在头部和尾部高效插入和删除元素(时间复杂度均为 O(1))。
- 既支持类似
vector的随机访问(通过下标访问元素),又支持在两端频繁插入删除,比vector更灵活。 - 内存布局不像
vector是连续存储,而是分段连续,适合频繁头尾操作。 deque内部由多个固定大小的缓冲区块组成,这些块被连续管理起来。- 通过一个指向块指针的数组管理这些缓冲区,实现高效插入删除与随机访问。
#include <iostream>
#include <deque>int main() {
std::deque<int> dq;// 在尾部添加元素
dq.push_back(10);
dq.push_back(20);// 在头部添加元素
dq.push_front(5);
dq.push_front(1);std::cout << "Deque中的元素有:";
for (int x : dq) {
std::cout << x << " ";
}
std::cout << std::endl;// 访问元素
std::cout << "第一个元素: " << dq.front() << std::endl;
std::cout << "最后一个元素: " << dq.back() << std::endl;// 删除元素
dq.pop_front();
dq.pop_back();std::cout << "删除首尾元素后:";
for (int x : dq) {
std::cout << x << " ";
}
std::cout << std::endl;// 使用下标访问
if (!dq.empty()) {
std::cout << "第一个元素(下标访问): " << dq[0] << std::endl;
}return 0;
}
deque 综合了性能和功能的平衡,成为 STL 中很多容器(尤其是适合头尾操作的适配器容器)的默认底层容器。
对比
| 容器 | 支持随机访问 | 头部插入删除 | 尾部插入删除 | 内存连续性 |
|---|---|---|---|---|
vector |
是 | 否 | 是 | 是 |
deque |
是 | 是 | 是 | 否(分段连续) |
list |
否 | 是 | 是 | 否(链表结构) |
| 操作类型 | vector |
deque |
list |
|---|---|---|---|
| 随机访问 | O(1)(常数时间) | O(1)(常数时间) | O(n)(线性时间) |
| 尾部插入 | 平均 O(1),扩容时 O(n) | O(1) | O(1) |
| 头部插入 | O(n) | O(1) | O(1) |
| 尾部删除 | O(1) | O(1) | O(1) |
| 头部删除 | O(n) | O(1) | O(1) |
| 中间插入/删除 | O(n) | O(n) | O(1)(给定迭代器位置) |
16.C++类中的六大成员函数
class MyClass {
public:
int* data;// 1. 默认构造函数
MyClass() : data(new int(0)) {
cout << "默认构造函数" << endl;
}// 2. 带参数构造函数
MyClass(int val) : data(new int(val)) {
cout << "带参数构造函数" << endl;
}// 3. 拷贝构造函数
MyClass(const MyClass& other) : data(new int(*other.data)) {
cout << "拷贝构造函数" << endl;
}// 4. 移动构造函数
MyClass(MyClass&& other) noexcept : data(other.data) {
cout << "移动构造函数" << endl;
other.data = nullptr;
}// 5. 拷贝赋值运算符
MyClass& operator=(const MyClass& other) {
cout << "拷贝赋值运算符" << endl;
if (this == &other) return *this;
delete data;
data = new int(*other.data);
return *this;
}// 6. 移动赋值运算符
MyClass& operator=(MyClass&& other) noexcept {
cout << "移动赋值运算符" << endl;
if (this == &other) return *this;
delete data;
data = other.data;
other.data = nullptr;
return *this;
}// 析构函数
~MyClass() {
cout << "析构函数" << endl;
delete data;
}
};
更多推荐

所有评论(0)