本人已熟练掌握C#,以下是我的C++学习笔记,自认为简洁且记录要点,发出来分享一下。
可拷贝至本地编辑器,方便折叠 。
熟悉区别后,接下来就是敲代码巩固实践啦!
欢迎大家与我交流讨论。
#include <iostream>
#include <vector>
#include <thread>
#include <stack>
#include <queue>
#include <unordered_map>
#include <map>
#include <unordered_set>
#include <set>
using namespace std;
thread_local int threadVar = 0;
void threadFunc() {
threadVar++;
std::cout << "ThreadVar: " << threadVar << std::endl;
}
//引用
void increment(int& x) {
x++;
}
int main()
{
#pragma region 一.输入输出
cout << "等待玩家输入:";
string input;
std::cin >> input;
//回车键会认为没有输入
std::cout << "等待玩家输入:" << input << endl;
//std:: 命名空间,引用后可省略
//cout << 命令一个命名空间中的对象去执行一个行为
//std::endl 换行
cout << "11111" << endl << "22222";
//等效于下面三行代码
cout << "11111";
cout << std::endl;
cout << "22222";
#pragma endregion
#pragma region 二.变量
#pragma region 1.短整数
// short\(signed) short (int)
// 存储范围:-32768 ~ 32767
// *无符号短整数:unsigned short (int) 存储范围:0 ~ 65535
short s = 1;
cout << s << endl;
//short的其他三种写法
signed short a = 1;
signed short int b = 1;
signed int e = 1;
#pragma endregion
#pragma region 2.整形
// int\(signed) int
// 存储范围:-21亿多 ~ 21亿多
// *无符号整数:unsigned (int) 存储范围:0 ~ 42亿多 后缀u/U
int i = 2, i2 = 3;
signed ri = 2;
#pragma endregion
#pragma region 3.长整形
// long\(signed) long (int)
// 存储范围:-21亿多 ~ 21亿多 或 -9223亿亿多 ~ 9223亿亿多
// 可以加上后缀,明确变量类型:l/L
// 注意:long的范围可能不同是因为在不同操作系统(Windows、UNIX / Linux等等)上
// 它所占的存储空间不同,在windows上它往往和int的存储空间一样
// *无符号长整形:unsigned long (int) 后缀ul/UL
long q = 3L;
#pragma endregion
#pragma region 4.长长整形
// long long\(signed) long long (int)
// 存储范围:-9223亿亿多 ~ 9223亿亿多
// 可以加上后缀,明确变量类型:ll/LL
// *无符号长长整形:unsigned long long(int) 后缀ull/ULL
long long ll = 2000000000LL;
#pragma endregion
#pragma region 5.浮点数
// float
// 存储范围:-3.4*10^-38 ~ 3.4*10^38
// 注意:
// 1.申明时在小数数值后面加f/F(原因:C++中声明的小数 默认是double类型)
// 2.存储6/7位有效数字(否则不能保证存储数值的准确性)
float t = 12.034f; //有效数字为5
t = 0.004f; //有效数字为1
t = 1.123f; //有效数字为4
#pragma endregion
#pragma region 6.双精度浮点数
// double
// 存储范围:-1.7*10^-308 ~ 1.7*10^308
// 注意:C++中声明的小数 默认是double类型
double d = 1.4;
#pragma endregion
#pragma region 7.长双精度浮点数
// long double
// 存储范围:-1.1*10^-4932 ~ 1.1*10^4932
// 加上后缀,明确变量类型:l/L
long double y = 1.4L;
#pragma endregion
#pragma region 8.布尔类型
// bool:true/false
// C++中 bool被当作整形对待 true:1 false:0
bool p = true;
p = 1;
#pragma endregion
#pragma region 9.字符类型
// char
// 存储单个字符,只用来存储ASCIII编码的字符
// C++中 char也能被当作整形对待 字符对应的整数就是它的ASCII码值
char c = 33; //ASCII码表对应“!”
cout << "字符类型:" << c << endl; //输出 !
c = 'w';
cout << "字符类型:" << c << endl; //输出 w
#pragma endregion
#pragma region 10.字符串类型
// string
// 存储多个字符(任何字符),采用的编码规则更广泛
string str = "c++";
#pragma endregion
#pragma region 11.枚举类型
// enum
// 用于定义一组命名的整数常量
// 枚举值可以显式地指定整数值,
// 例如 enum Day { Sunday = 1, Monday, Tuesday, ... };
// 此时 Sunday 的值为 1,Monday 为 2,依此类推。
enum Day {
Sunday, // 默认值为0
Monday, // 1
Tuesday, // 2
Wednesday, // 3
Thursday, // 4
Friday, // 5
Saturday // 6
};
#pragma endregion
#pragma region 12.指针类型
// type* / 数据类型* 指针变量名
// 用于表示指向类型为type的对象的指针
int x = 10;
int* ptr = &x; // ptr 存储了变量 x 的内存地址
cout << &x << endl; // 输出 x 的地址
cout << ptr << endl; // 输出 ptr 的值(即 x 的地址)
cout << *ptr << endl; // 输出 10,因为 *ptr 访问的是 x 的值
*ptr = 100; // 通过指针修改 x 的值
cout << x << endl; // 输出修改后的 x 的值
#pragma endregion
#pragma region 13.数组类型
// type[] / type[size]
// 注意:
// size必须是常量
// 数组的大小是固定的,可以通过 sizeof 运算符计算数组的大小。
// 数组名本质上是一个指向数组第一个元素的指针。
//如果数组未完全初始化,剩余元素会被初始化为 0
int arr[5] = { 1, 2 }; //arr = {1, 2, 0, 0, 0}
int* pt = arr; // ptr 指向数组的第一个元素
cout << *pt << endl; // 输出 10
cout << *(pt + 1) << endl; // 输出 20
int size = 5;
//int arr[size]; // 错误:size 不是常量
int matrix[2][3] = { //二维数组
{1, 2, 3},
{4, 5, 6}
};
//使用 new 和 delete 运算符动态分配和释放数组
int* array = new int[5]; // 动态分配一个包含 5 个整数的数组
array[0] = 10;
delete[] array; // 释放数组内存
int* ptrArray[5]; // 定义一个包含 5 个 int* 类型指针的数组
#pragma endregion
#pragma region 14.vector动态数组
// 使用 std::vector 需要包含头文件 <vector>
// 动态大小、连续存储、自动内存管理、高效访问
// 类似于C#的list
//定义和初始化:可存储任何类型
std::vector<int> vec = { 1, 2, 3, 4 ,5 };// 初始化一个包含 5 个元素的 vector
std::vector<int> vec1(10); // 定义一个包含 10 个元素的 vector,默认值为 0
std::vector<int> vec2(10, 42); // 定义一个包含 10 个元素的 vector,每个元素初始化为 42
//访问元素:使用 [] 或 at() 访问元素
//[] 不会检查索引是否越界。
//at() 会检查索引是否越界,如果越界会抛出 std::out_of_range 异常。
int x1 = vec[0]; // 访问第一个元素
int y1 = vec.at(0); // 访问第一个元素
//添加和删除元素:push_back()、pop_back()
vec.push_back(6); // 在末尾添加元素6
vec.pop_back(); // 删除末尾元素
int size1 = vec.size(); // 获取元素个数
int cap = vec.capacity(); // 获取容量
//it的类型是 std::vector<int>::iterator() 迭代器
//迭代器的行为类似于指针,但它是一个类对象,封装了访问容器元素的逻辑。
auto it = vec.begin();
it = vec.erase(it); // 删除迭代器 it 指向的元素(删除 1,it 指向下一个元素(2))
vec.insert(it, 100); //在迭代器 it 指向的位置插入元素 x (在 2 前插入 100)
//遍历元素
std::cout << "for循环遍历vector:";
for (int x : vec) {
std::cout << x << " ";
}
std::cout << endl << "迭代器遍历vector:";
for (auto it = vec.begin(); it != vec.end(); it++) {
int x = 1;
std::cout << *it << " ";
}
int n = 3;
//其他常见操作
bool isEmpty = vec.empty(); //判断是否为空
auto begin = vec.begin(); //返回指向第一个元素的迭代器
auto end = vec.end(); //返回指向末尾(最后一个元素之后)的迭代器
int front = vec.front(); //返回第一个元素
int back = vec.back(); //返回最后一个元素
vec.clear(); //清空所有元素
vec.resize(n); //调整大小为 n,多出的元素用默认值填充
vec.reserve(n); //预留至少 n 个元素的存储空间,但不改变大小
#pragma endregion
#pragma region 15.结构体类型
//struct 结构体名 {
// 数据类型 成员1;
// 数据类型 成员2;
// // ...
//};
// 结构体可以嵌套其他结构体
// 可以定义结构体数组
// 可以使用指针访问结构体成员
// struct 的成员默认是 public 的。
struct Person {
std::string name;
int age;
double height;
};
Person p1; // 定义一个 Person 类型的变量 p1
p1.name = "Alice";
p1.age = 25;
p1.height = 1.68;
Person p2 = { "Bob", 30, 1.75 };
std::cout << endl << "输出结构体成员: " << endl;
std::cout << "Person 1: " << p1.name << ", " << p1.age << ", " << p1.height << std::endl;
std::cout << "Person 2: " << p2.name << ", " << p2.age << ", " << p2.height << std::endl;
#pragma endregion
#pragma region 16.类类型
//类是 C++ 中面向对象编程的核心概念,支持封装、继承和多态。
// 类的成员可以通过访问修饰符控制访问权限。
// 类可以包含构造函数、析构函数、成员变量和成员函数。
class Student {
private:
std::string name;
int age;
public:
// 构造函数
Student(std::string n, int a) : name(n), age(a) {}
// 析构函数
~Student() {
std::cout << "Object destroyed" << std::endl;
}
void display() {
std::cout << "Name: " << name << ", Age: " << age << std::endl;
}
};
Student student("Alice", 12); // 定义一个 Student 类的对象 p1
student.display();
#pragma endregion
#pragma region 17.共用体类型
// 一种特殊的数据类型,允许在相同的内存位置存储不同的数据类型
// 特点:
// 1.共享内存:
// 共用体的所有成员共享同一块内存空间
// 修改一个成员的值会影响其他成员的值
// 2.大小
// 共用体的大小是其最大成员的大小
// 3.同一时间只能存储其中一个成员的值
// 4.应用场景
// 节省内存:当需要在同一内存位置存储不同类型的数据时,可以使用共用体来节省内存。
// 类型转换:以不同的方式解释同一块内存
// 硬件编程:在嵌入式系统或硬件编程中,共用体常用于访问寄存器的不同部分。
union Data {
int i;
float f;
char c;
};
Data data;
data.i = 10; // 存储 int 值
data.f = 3.14; // 存储 float 值
// 修改一个成员的值后,其他成员的值会变得无效。
// 修改 f 的值后,i 的值变得无效
data.i = 1092616192; // 0x41200000
std::cout << "共用体类型中类型转换:" << data.f << endl; // 输出 10.0
#pragma endregion
#pragma region 18.基于哈希表类型
// 优点:查找、插入、删除操作效率高。
// 缺点:无法保证元素顺序,哈希冲突时性能会下降。
// 1.unordered_map
unordered_map<string, int> hashTable;
hashTable["apple"] = 10;
cout << hashTable["apple"]; // 输出 10
// 2.unordered_set
unordered_set<int> numbers = { 1, 2, 3, 4, 5 };
numbers.insert(6); // 插入元素
numbers.insert(2); // 不会重复插入
if (numbers.find(3) != numbers.end()) { // 查找元素
std::cout << "3 found in set\n";
}
numbers.erase(4); // 删除元素
for (const auto& num : numbers) { // 遍历元素
std::cout << num << " ";
}
// 大小和容量
std::cout << "\nunordered_set大小: " << numbers.size();
std::cout << "\nunordered_set容量: " << numbers.bucket_count();
#pragma endregion
#pragma region 19.基于红黑树类型
// 1.映射(Map)
// 一种有序的键值对容器,底层实现是红黑树。
// 优点:元素有序,适合需要按顺序处理数据的场景。
// 缺点:操作效率比 unordered_map 略低。
map<string, int> myMap;
myMap["apple"] = 10;
cout << myMap["apple"]; // 输出 10
// 2.set
// 存储唯一元素并自动排序
// 提供了高效的查找、插入和删除操作,时间复杂度均为 O(log n)
set<int> numbers_set = { 5, 2, 4, 1, 3 }; // 创建set
// 插入元素
numbers_set.insert(6);
numbers_set.insert(2); // 不会重复插入
// 查找元素
if (numbers_set.find(3) != numbers_set.end()) {
std::cout << "3 found in set\n";
}
// 删除元素
numbers_set.erase(4);
// 遍历元素(自动排序)
for (const auto& num : numbers_set) {
std::cout << num << " "; // 输出:1 2 3 5 6
}
// 大小
std::cout << "\nSize: " << numbers_set.size();
#pragma endregion
#pragma region 20.其他
// 1.链表
struct Node {
int data;
Node* next;
};
Node* head = nullptr;
Node* newNode = new Node{ 10, nullptr };
head = newNode; // 插入新节点
// 2.栈(stack)
// 应用场景:常用于递归、深度优先搜索等场景。
stack<int> sta;
sta.push(1);
sta.push(2);
cout << sta.top(); // 输出 2
sta.pop();
// 3.队列(queue)
// 应用场景:常用于广度优先搜索、任务调度等场景。
queue<int> que;
que.push(1);
que.push(2);
cout << que.front(); // 输出 1
que.pop();
// 4. 双端队列(deque)
// 双端队列允许在两端进行插入和删除操作,是栈和队列的结合体。
// 应用场景:适合需要在两端频繁操作的场景。
deque<int> dq;
dq.push_back(1);
dq.push_front(2);
cout << dq.front(); // 输出 2
dq.pop_front();
#pragma endregion
#pragma endregion
#pragma region 三.存储类
#pragma region 1.auto
// 在 C++11 之前,auto 用于表示局部变量的默认存储类(现已废弃)
// 在 C++11 及之后,auto 用于类型推导。
// 生命周期:自动
auto x3 = 10; // x 的类型被推导为 int
auto y3 = 3.14; // y 的类型被推导为 double
#pragma endregion
#pragma region 2.register
//提示编译器将变量存储在 CPU 寄存器中,以提高访问速度。
//现代编译器通常会自动优化,所以C++11 开始,register 被弃用;C++17 中完全移除。
register int counter = 0; // 提示编译器将 counter 存储在寄存器中
#pragma endregion
#pragma region 3.static
// 用于局部变量时,使变量的生命周期延长至整个程序运行期间。
// 用于全局变量或函数时,限制其作用域为当前文件。
// 生命周期:自动
static int count = 0; // 局部静态变量
#pragma endregion
#pragma region 4.extern
// 用于扩展全局变量或函数的作用域,使其可以在多个文件中共享
// 生命周期:整个程序运行期间
//
// file1.cpp
// int globalVar = 42; // 定义全局变量
// file2.cpp
// extern int globalVar; // 声明全局变量
// std::cout << "GlobalVar: " << globalVar << std::endl;
#pragma endregion
#pragma region 5.thread_local
// 线程局部变量在每个线程中独立存在,互不干扰。
// 适用于多线程编程。#include <thread>
// 生命周期:线程生命周期
thread t1(threadFunc);
thread t2(threadFunc);
t1.join();
t2.join();
#pragma endregion
#pragma region 6.mutable
// const用于声明变量或对象为常量,即它们的值在初始化后不能被修改。
// 当 const 用于函数参数时,它表示该参数在函数内部是只读的,不能被修改。
// mutable 变量即使在 const 对象中也可以被修改。
// 通常用于缓存或标记等场景。
// 生命周期:与对象相同
class Example {
private:
mutable int cache; // mutable 变量
int value;
public:
Example(int v) : value(v), cache(0) {
// 构造函数
}
int getValue() const {
cache++; // 即使在 const 函数中也可以修改 cache
return value;
}
};
Example ex(10);
cout << "mutable:" << ex.getValue() << endl;
#pragma endregion
#pragma region 7.引用
// type& 引用的名称 = 被引用的变量名称;
//
// 引用:一种数据类型,它允许一个变量以另一个变量的别名存在。
// 引用传递,在功能上类似于C#的ref。
// 特点:
// 引用必须在声明时初始化,并且不能重新绑定到另一个对象。
// 引用不占用额外的存储空间,直接指向被引用的对象。
// 使用场景:
// 当函数需要直接操作一个对象时,使用引用可以避免不必要的拷贝,提高性能。
// 当函数需要修改传入的对象时,使用引用可以明确表达这一意图。
int reference = 10;
increment(reference); // a 的值变为 11
#pragma endregion
#pragma endregion
#pragma region 四.循环
#pragma region 1.循环类型
// 1.while
// 2.for
// 3.do...while
#pragma endregion
#pragma region 2.循环控制语句
// 1.break: 终止 loop 或 switch 语句,程序流将继续执行紧接着 loop 或 switch 的下一条语句。
// 2.continue: 跳过当次循环。
// 3.goto: 将控制转移到被标记的语句。但是不建议在程序中使用 goto 语句。
#pragma endregion
#pragma endregion
//return 0
//这句代码可以省略 C++当中对于main函数会进行特殊处理
//如何不写返回,编译器也会自动返回0
}
#pragma region 五.面向对象
#pragma region 1.继承
/*
class DerivedClass : public / protected / private BaseClass{
// 派生类的成员
};
*/
// ———————————————————————————————————
// 继承方式 基类共有成员 基类保护成员 基类私有成员
// ———————————————————————————————————
// 公有继承 公有 保护 不可访问
// 保护继承 保护 保护 不可访问
// 私有继承 私有 私有 不可访问
// ———————————————————————————————————
// 继承: 允许派生类继承基类的属性和方法。
// 继承方式: 公有继承、保护继承和私有继承,决定了派生类如何访问基类的成员。
// 多态: 通过虚函数实现,允许派生类对象通过基类指针或引用调用不同的实现。
// 构造函数和析构函数:派生类的构造函数需要显式调用基类的构造函数。
// 虚继承: 用于解决多重继承中的菱形问题。
class Animal {
public:
Animal() {
cout << "Animal类构造函数" << endl;
}
void eat() {
cout << "Animal类eat函数" << endl;
}
};
class Dog : public Animal {
};
#pragma endregion
#pragma region 2.重载与重写
//1.重载:与C#概念相同,允许使用相同的函数名定义多个函数版本,只要它们的参数列表不同即可。
// 不能仅通过返回类型来区分重载函数。
// 如果多个重载函数都可以匹配调用,编译器会报错。
// 重载运算符,使得类的对象可以使用标准运算符进行操作。
//2.重写:与C#概念相同,派生类覆盖基类虚函数的过程,用于实现多态。
// 使用 override 关键字可以明确表示覆盖,提高代码的可读性。
// 使用 final 关键字可以禁止进一步覆盖。
//重写与隐藏的区别在于是否涉及虚函数以及参数列表是否一致。
class Point {
public:
int x, y;
// 构造函数,用于初始化点的坐标
Point(int x, int y) : x(x), y(y) {}
// 重载加法运算符
Point operator+(const Point& p) {
return Point(x + p.x, y + p.y);
}
// operator+ 是C++中用于重载加法运算符的特殊函数名称。
// 它告诉编译器,当使用 + 运算符时,应该调用这个函数。
// Point&:表示一个对 Point 类型对象的引用。
};
int main() {
Point p1(1, 2);
Point p2(3, 4);
Point p3 = p1 + p2; // 使用重载的加法运算符
cout << "Result: (" << p3.x << ", " << p3.y << ")" << endl;
return 0;
}
//重写
class Base {
public:
// 虚函数:与C++虚函数相同概念
virtual void display() {
cout << "基类虚函数" << endl;
}
void display1(int x) {
cout << "基类普通函数" << endl;
}
virtual void makeSound() final { // 禁止派生类重写
cout << "Animal makes a sound" << endl;
}
};
class Derived : public Base {
public:
void display() override {
cout << "派生类重写虚函数" << endl;
}
void display1() {
cout << "派生类隐藏基类的普通函数,哪怕参数列表不同" << endl;
}
};
#pragma endregion
#pragma region 3.多态
// C++ 主要通过 虚函数(Virtual Functions) 和 继承 实现多态。
// 重写(override) 是 C++ 实现 运行时多态 的唯一方式(通过虚函数)。
// 重载(overload) 属于 编译时多态,但并非经典多态的核心机制。
// 应用场景:
// 1.统一接口,不同实现(如 GUI 控件、游戏角色)。
// 2.运行时动态绑定(如插件系统、回调机制)。
// 3.工厂模式(根据输入创建不同的派生类对象)。
//*纯虚函数与抽象类 ==> C#的抽象函数与抽象类
class Animal {
public:
// 纯虚函数:没有实现的虚函数,强制派生类必须重写
virtual void makeSound() = 0;
};
class Dog : public Animal {
public:
void makeSound() override {
cout << "Dog barks: Woof!" << endl;
}
};
Animal animal; // 错误!Animal 是抽象类
Animal* animal = new Dog(); // 正确,可以用基类指针指向派生类对象
#pragma endregion
#pragma region 4.数据抽象
// 通过隐藏对象的内部实现细节,仅暴露必要的接口来操作数据。
// 将"做什么"(接口)与"怎么做"(实现)分离,从而提高代码的可维护性、安全性和复用性。
// ---------------------------------------------------------------------------------
// 优势 说明
// ---------------------------------------------------------------------------------
// 安全性 防止外部代码直接修改内部数据(如禁止负数余额)。
// 可维护性 修改内部实现(如优化余额计算逻辑)不影响外部代码。
// 接口稳定性 即使内部数据结构变化(如用 long 替代 double 存储金额),接口保持不变。
// 代码复用 通过类封装通用功能(如所有银行账户共享相同的操作接口)。
// ---------------------------------------------------------------------------------
#pragma endregion
#pragma region 5.数据封装
// 封装:捆绑数据与行为、访问控制、隐藏实现细节
// 封装是实现数据抽象的技术手段。
// 如何实现数据封装?
// 1.定义类并划分访问权限
// 2.通过对象调用公共接口
#pragma endregion
#pragma region 6.数据接口
// C++ 接口是使用抽象类来实现的
#pragma endregion
#pragma endregion