C++现代实用教程(一):基础主线(VSCODE)学习笔记
月光常常常到故里
前言
b站up主原子之音的C++现代实用教程(一):基础主线(VSCODE)的学习笔记以及源码分享,
原视频链接:C++现代实用教程(一):基础主线(VSCODE)
- C++开发环境搭建打印函数配置Vscode支持Gcc、Clang、MSVC
- 变量与常量、数据类型、位运算
- 选择语句、循环语句、三元表达式、数学函数
- C++中的函数:函数基础概念、值传递、函数重载、函数递归调用、Lambda函数
- C++中的引用与指针:指针的基本概念、堆栈、指针常见错误、引用的基本概念、引用与指针的区别、函数指针传递和引用传递、const指针、指针函数与回调Callback、左值与右值
- 字符与字符串:字符的操作、cString、String
- 多文件编译:配置vscode支持多文件编译
打印
一、hello world!
#include <iostream>
using namespace std;
int main()
{
cout << "Hello World!" << endl;
return 0;
}
二、打印的两种方式
#include <iostream>
#include <string> // 包含C++字符串处理库,使用std::string而不是C风格的字符数组
using namespace std; // 避免在使用cin和cout时每次都加上std::
int main(int argc, char *argv[]) {
string num1, num2;
// 用户输入
cout << "请输入第一个数字:" << endl;
cin >> num1;
cout << "请输入第二个数字:" << endl;
cin >> num2;
// 将字符串转换为整数
int number1 = stoi(num1);
int number2 = stoi(num2);
// 使用cout输出结果
cout << number1 << " + " << number2 << " = " << number1 + number2 << endl;
// 使用printf(C风格)输出结果
printf("%d + %d = %d\n", number1, number2, number1 + number2);
// 处理命令行参数
if (argc >= 1) {
cout << "你提供了 " << argc << " 个参数" << endl;
for (int i = 0; i < argc; ++i) {
cout << "参数 " << i << ": " << argv[i] << endl;
}
}
return 0;
}
变量与常量、数据类型、位运算
一、变量与常量
#include <iostream>
using namespace std;
int main()
{
// 定义两个整数变量并初始化
// 变量声明示例: 声明一个整数类型的变量,并赋初值为3
// 输出示例: 输出整数值到控制台
// 字符串输出例子: 输出字符串到控制台
// 变量声明和初始化
int a1 = 3;
int a2{3};
cout << a1 << a2; // 输出变量a1和a2的值
// 变量声明说明
// 在C++中,声明变量时可以直接初始化,使用大括号{}也可以完成初始化。
// 常量声明示例
const float pi = 3.14; // 使用const关键字定义常量
// 另一种常量声明方式是使用宏定义 #define PI 3.14
// 但是使用const关键字比宏定义更安全,因为它有类型检查。
return 0;
}
二、位操作与逻辑运算
#include <iostream>
using namespace std;
int main() {
int a = 5, b = 3;
// 按位与 &
cout << "0101 & 0011 = " << (a & b) << endl; // 结果是 0001,十进制为 1
// 按位或 |
cout << "0101 | 0011 = " << (a | b) << endl; // 结果是 0111,十进制为 7
// 按位非 ~
cout << "~0101 = " << (~a) << ", ~0011 = " << (~b) << endl;
// 在32位系统中,~5 的结果是 -6(补码表示法)
// 左移 <<
cout << "0101 << 2 = " << (a << 2) << endl; // 结果是 010100,十进制为 20
// 右移 >>
cout << "0101 >> 1 = " << (a >> 1) << endl; // 结果是 0010,十进制为 2
// 逻辑运算符:
// 逻辑与 &&
cout << "true && true = " << (true && true) << endl; // 结果为 1 (true)
cout << "true && false = " << (true && false) << endl; // 结果为 0 (false)
// 逻辑或 ||
cout << "true || false = " << (true || false) << endl; // 结果为 1 (true)
cout << "false || false = " << (false || false) << endl; // 结果为 0 (false)
// 逻辑非 !
cout << "!true = " << (!true) << endl; // 结果为 0 (false)
cout << "!false = " << (!false) << endl; // 结果为 1 (true)
}
三、数据类型
#include <iostream>
using namespace std;
int main()
{
// 定义不同类型的变量:bool, char, int, float, double 等
// 使用 sizeof 操作符可以获取各种数据类型在当前系统上的大小(以字节为单位)
// sizeof 返回的结果是编译时确定的,因此它是一个常量表达式
float a;
cout << "float: " << sizeof(a) << " bytes\n"
<< "double: " << sizeof(double) << " bytes\n"
<< "int: " << sizeof(int) << " bytes\n"
<< "char: " << sizeof(char) << " bytes\n"
<< "long: " << sizeof(long) << " bytes\n"
<< "long long: " << sizeof(long long) << " bytes\n"
<< "unsigned long long: " << sizeof(unsigned long long) << " bytes\n"
<< "size_t: " << sizeof(size_t) << " bytes\n";
// auto 关键字可以让编译器自动推断变量的类型
// 这样可以简化代码,特别是在类型名称较长或复杂的情况下
// 例如:
auto value1 = 42; // 编译器推断为 int
auto value2 = 3.14f; // 编译器推断为 float
auto value3 = 2.71828; // 编译器推断为 double
auto value4 = 'A'; // 编译器推断为 char
auto value5 = true; // 编译器推断为 bool
auto value6 = string("C++");// 编译器推断为 std::string
// 输出使用 auto 推断类型的变量值及其类型大小
cout << "\nUsing auto keyword:\n";
cout << "value1 (int): " << sizeof(value1) << " bytes\n";
cout << "value2 (float): " << sizeof(value2) << " bytes\n";
cout << "value3 (double): " << sizeof(value3) << " bytes\n";
cout << "value4 (char): " << sizeof(value4) << " bytes\n";
cout << "value5 (bool): " << sizeof(value5) << " bytes\n";
cout << "value6 (std::string): " << sizeof(value6) << " bytes\n"; // 注意:sizeof 对于类类型可能不会显示实际内存占用
return 0;
}
选择语句、循环语句、三元表达式、数学函数
一、选择语句的两种情况
#include <bits/stdc++.h>>
using namespace std;
int main()
{
int num1, num2;
char operation; // 用户选择的操作符
cout << "输入第一个数字: ";
cin >> num1;
cout << "选择操作 (+, -, *, /): ";
cin >> operation;
cout << "输入第二个数字: ";
cin >> num2;
// if-else像是双岔路一般
if (operation == '+')
{
cout << "if-else结果是: " << num1 + num2 << endl;
}
else if (operation == '-')
{
cout << "if-else结果是: " << num1 - num2 << endl;
}
else if (operation == '*')
{
cout << "if-else结果是: " << num1 * num2 << endl;
}
else if (operation == '/')
{
if (num2 != 0)
{
cout << "if-else结果是: " << (float)num1 / num2 << endl;
}
else
{
cout << "ie除数不能为零!" << endl;
}
}
else
{
cout << "ie无效的操作符!" << endl;
}
// switch则像是多岔路
switch (operation)
{
case '+':
cout << "switch结果是: " << num1 + num2 << endl;
break;
case '-':
cout << "switch结果是: " << num1 - num2 << endl;
break;
case '*':
cout << "switc结果是: " << num1 * num2 << endl;
break;
case '/':
if (num2 != 0)
{
cout << "switch结果是: " << (float)num1 / num2 << endl;
}
else
{
cout << "sw除数不能为零!" << endl;
}
break;
default:
cout << "sw无效的操作符!" << endl;
break;
}
// 三目运算符 表达式 ? 表达式 : 表达式
string result = (num1 > num2) ? "num1>num2" : "num1 <= num2";
cout << result;
}
二、循环结构
#include <iostream>
using namespace std;
int main()
{
// 1. for 循环
cout << "For 循环:" << endl;
for (int i = 0; i < 5; ++i)
{
cout << "For 迭代 " << i << endl;
}
cout << "For 循环结束." << endl
<< endl;
// 2. while 循环
cout << "While 循环:" << endl;
int j = 0;
while (j < 5)
{
cout << "While 迭代 " << j << endl;
++j;
}
cout << "While 循环结束." << endl
<< endl;
// 3. 死循环 使用 break 跳出
cout << "使用 break 的死循环:" << endl;
int k = 0;
while (true)
{ // 死循环
cout << "死循环迭代 " << k << endl;
if (k >= 4)
{
cout << "在 " << k << " 次迭代后跳出死循环." << endl;
break; // 跳出死循环
}
++k;
}
cout << "使用 break 的死循环结束." << endl
<< endl;
// 4. 死循环 使用 continue 跳过当前循环体剩余部分
cout << "使用 continue 的死循环:" << endl;
int l = 0;
while (l < 10)
{ // 设置一个有限次数的循环以便观察效果
if (l % 2 == 0)
{
cout << "跳过偶数迭代 " << l++ << " 并继续下一次迭代." << endl;
continue; // 跳过当前循环体剩余部分,直接进入下一次迭代
}
// 下面这行只有在 l 是奇数时才会被执行
cout << "处理奇数迭代 " << l << "." << endl;
++l;
}
cout << "使用 continue 的死循环结束." << endl;
return 0;
}
三、cmath/math.h
#include <iostream>
#include <cmath> // 包含数学函数
#include <cstdlib> // 包含随机数生成函数
#include <ctime> // 包含时间函数
using namespace std;
int main()
{
// 数学函数示例
cout << "abs(-19) = " << abs(-19) << endl; // abs: 返回绝对值
cout << "max(5, 4) = " << max(5, 4) << endl; // max: 返回两个数中的较大值
cout << "min(5, 4) = " << min(5, 4) << endl; // min: 返回两个数中的较小值
cout << "floor(10.45) = " << floor(10.5) << endl; // floor: 返回不大于给定值的最大整数
cout << "round(10.45) = " << round(10.5) << endl; // round: 四舍五入到最接近的整数
cout << "ceil(10.45) = " << ceil(10.5) << endl; // ceil: 返回不小于给定值的最小整数
cout << "pow(2, 3) = " << pow(2, 3) << endl; // pow: 计算幂运算
cout << "sqrt(100) = " << sqrt(100) << endl; // sqrt: 计算平方根
cout << endl;
// 指数函数示例
cout << "exp(1) = " << exp(1) << endl; // exp: 计算自然指数 e 的幂
cout << "exp2(1) = " << exp2(1) << endl; // exp2: 计算 2 的幂
cout << "exp2(2) = " << exp2(2) << endl; // exp2: 计算 2 的幂
cout << "log(20) = " << log(20) << endl; // log: 计算自然对数
cout << "log2(8) = " << log2(8) << endl; // log2: 计算以 2 为底的对数
cout << "log5^7 = " << log(7) / log(5) << endl; // 换底公式求任意底数
// 随机函数示例
srand(time(NULL)); // srand: 初始化随机数生成器,使用当前时间给下面的rand函数提供种子
for (size_t i = 0; i < 10; i++) // 生成 10 个随机数
{
int secret = rand() % 10; // rand: 生成随机数,% 10 限制范围在 0 到 9 之间
cout << "secret : " << secret << endl;
}
}
函数
一、函数的声明、创建及调用
#include <iostream>
using namespace std;
// 函数声明:声明函数的原型,告诉编译器函数的返回类型、名称和参数列表
int factorial(int);
int main()
{
int number = 0;
cout << "输入n: ";
cin >> number; // 实参:在函数调用中传递的实际值
// 函数调用:调用已声明的函数,并传递实参
cout << "n的阶乘: " << factorial(number) << endl;
return 0;
}
// 函数定义:定义函数的具体实现
int factorial(int number) // 形参:函数定义中声明的参数变量
{
int result = 1;
for (size_t i = 1; i <= number; i++)
{
result = result * i;
}
return result;
}
二、函数值传递与引用传递(指针传递)
#include <iostream>
using namespace std;
// 函数声明
int addAge(int); // 声明一个返回整数的函数 addAge,参数为整数
void addAgePoint(int *const); // 声明一个返回 void 的函数 addAgePoint,参数为指向整数的常量指针
int main()
{
int age{45}; // 定义一个整数变量 age 并初始化为 45
// 输出初始值
cout << "初始年龄: " << age << endl;
cout << "年龄的地址: " << &age << endl
<< endl;
// 调用 addAge 函数
cout << "调用 addAge 函数---" << endl;
int newAge{addAge(age)}; // 调用 addAge 函数,并将结果赋值给 newAge
cout << "新年龄(addAge返回值): " << newAge << endl;
cout << "原始年龄(addAge调用后): " << age << endl;
cout << "原始年龄的地址(addAge调用后): " << &age << endl
<< endl;
// 调用 addAgePoint 函数
cout << "调用 addAgePoint 函数---" << endl;
addAgePoint(&age);
cout << "原始年龄(addAgePoint对指针操作后): " << age << endl;
cout << "原始年龄的地址(addAgePoint对指针操作后): " << &age << endl;
cout << "---------------" << endl;
}
// 函数定义:值传递
int addAge(int age)
{
int result{age + 1}; // 在函数内部创建一个新的局部变量 result,并将其值设置为 age + 1
return result; // 返回 result 的值
}
// 函数定义:引用传递
void addAgePoint(int *const age)
{
++(*age); // 通过指针修改 age 的值,使其加 1
cout << "(addAgePoint内的)年龄: " << *age << " " << "(addAgePoint函数指针) 地址: " << &age << endl; // 输出 age 的值和地址
//一个变量的引用其实是指向该变量的其他指针
}
三、函数重载
#include <iostream>
using namespace std;
// 函数重载:两个函数具有相同的函数名,但参数列表不同
// 第一个 max 函数,参数为两个整数
int max(int a, int b)
{
// 返回较大的整数值
return (a > b) ? a : b;
}
// 第二个 max 函数,参数为两个 double 类型的数
double max(double a, double b)
{
// 返回较大的 double 值
return (a > b) ? a : b;
}
int main(int argc, char *argv[])
{
double x{1.3};
double y{2.4};
int a{1};
int b{45};
// 调用 max 函数,参数为两个 double 类型的数
cout << "max(x, y): " << max(x, y) << endl;
// 调用 max 函数,参数为两个整数
cout << "max(a, b): " << max(a, b) << endl;
}
四、函数的递归调用
#include <iostream>
using namespace std;
// 递归函数:计算阶乘
int Factorial(int number);
int main()
{
int count{0};
cout << "请输入一个整数: ";
cin >> count;
// 调用递归函数计算阶乘
cout << "Factorial第" << count << "项 : " << Factorial(count) << endl;
}
// 递归函数:计算阶乘
int Factorial(int number)
{
// 终止条件:如果 number 为 1,则返回 1
if (number == 1)
{
return 1;
}
else
{
// 递归调用:计算 number * Factorial(number - 1)
int result = number * Factorial(number - 1);
return result;
}
}
五、lamda表达式(匿名函数)
Lambda 表达式示例:
cpp auto add = [](int a, int b) -> int { return a + b; };
[]:
捕获列表,这里没有捕获任何外部变量
(int a, int b):
参数列表,接受两个整数参数 a 和 b
-> int:
返回类型,返回一个整数
{ return a + b; }:
函数体,返回 a 和 b 的和
cpp auto multiply = [](int a, int b) -> int { return a * b; };
类似于 add,但返回 a 和 b 的乘积
Lambda 表达式的捕获列表形式:
- 按值捕获:[x],捕获变量 x 的值,在函数表达式内为不可修改的左值/只读状态的变量
- 按引用捕获:[&x],捕获变量 x 的引用
- 按值捕获:[=],捕获所有外部变量的值,在函数表达式内为不可修改的左值/只读状态的变量
- 按引用捕获:[&],捕获所有外部变量的引用
为什么要用auto:
-
- 简化代码:传统方式需要明确写出迭代器的具体类型,使用 auto 可以让编译器自动推导变量的类型
-
- 增强可维护性:当改变一个容器或者数据结构的类型时,如果使用了 auto 来声明变量,则不需要同时修改所有相关变量的声明
-
- 提高代码的通用性和复用性:在编写泛型代码(如模板函数)时,auto 允许你定义能够处理多种类型的变量,而无需显式地指定每种可能的类型
-
- 处理复杂类型:对于像 lambda 表达式这样的匿名函数对象,其具体类型通常难以直接表达,因此 auto 成为了最佳的选择
-
- 其他:提升性能、支持现代 C++ 特性
#include <iostream>
using namespace std;
int main()
{
cout << "a=5 b=3 x=3 y=4" << endl;
cout << "a与b的传参加减乘除" << endl;
// 加
auto add = [](int a, int b) -> int
{
return a + b;
}; // lamda表达式是表达式
// 最后需要加分号
cout << "Addition: " << add(5, 3) << endl;
// 减
auto subtract = [](int a, int b) -> int
{
return a - b;
};
cout << "Subtraction: " << subtract(5, 3) << endl;
// 乘
auto multiply = [](int a, int b) -> int
{
return a * b;
};
cout << "Multiplication: " << multiply(5, 3) << endl;
// 除
auto divide = [](int a, int b) -> int
{
return b != 0 ? a / b : 0;
};
cout << "Division: " << divide(5, 3) << endl;
cout << "x和y的捕获情况" << endl;
// 四种捕获形式的数据输入
int x = 10;
int y = 20;
// 按值捕获所有外部变量
auto capture1 = [=]()
{
cout << "capture1 x: " << x << " " << "y: " << y << endl;
// x++;y++ 修改不了的左值
};
// 引用捕获所有外部变量
auto capture2 = [&]()
{
cout << "capture2 ++x: " << ++x << " " << "++y: " << ++y << endl;
};
// 按值捕获指定外部变量
auto capture3 = [x]()
{
cout << "capture3 x: " << x << endl;
};
// 捕获指定外部变量的引用
auto capture4 = [&y]()
{
cout << "capture4 y: " << y << endl;
};
capture1();
capture2();
capture3();
capture4();
cout << "取余的捕获和传参混用" << endl;
// 取余
auto remainder = [=](int a, int b)
{
if (b != 0)
{
cout << "Remainder x%y(捕获参数) : " << x % y << endl;
cout << "Remainder a%b(传入参数) : " << a % b << endl;
}
};
remainder(15, 4);
return 0;
}
引用与指针
一、 指针、堆、栈
程序运行时的空间和变量"类别"
- 空间上有代码区、常量区、全局或静态数据区、堆、栈、其他存储位置(寄存器)
代码区:代码区是用来存放程序执行代码的内存区域。这部分区域存储的是可执行的机器指令,这些指令在程序运行过程中是只读的。例如,当你编写一个 C/C++ 程序并编译后,函数的定义、循环语句、条件判断语句等对应的机器码都存储在代码区。其内容在程序运行期间一般不会改变,这是为了保证程序的指令完整性和安全性。
常量区:常量区用于存储程序中的常量数据,如字符串常量、被声明为const的常量等。在 C/C++ 等语言中,当你定义一个字符串常量,例如char *str = “Hello World”;,这个字符串 "Hello World"就存储在常量区。这些常量在程序运行期间其值不会改变,而且和代码区类似,常量区的数据通常也是只读的。这有助于保护数据的一致性,防止意外修改。
全局或静态数据区:在内存中有其固定的分配区域,这个区域在程序编译时就大致确定了位置和大小范围。它存储的数据通常是在程序启动时就初始化好的全局变量和静态变量。
堆:也是内存中的一块区域,不过它的分配是动态的。操作系统通过内存管理机制来管理堆空间,程序可以在运行过程中根据需要请求分配和释放堆内存。与全局或静态数据区在存储位置上是分开的,它有自己独立的一套管理机制,在 C/C++ 中,通过malloc和free等函数来操作堆内存。
栈:同样是内存中的特定区域,它主要用于函数调用和局部变量存储。栈的增长和收缩是按照函数调用的顺序来进行的,有自己严格的后进先出规则。在物理存储上与堆和全局 / 静态数据区也是分开的。
寄存器:是 CPU 内部的存储单元,和内存(包括全局 / 静态数据区、堆、栈所在的内存)完全是不同的硬件层面的存储。它们在物理位置上独立于内存,用于快速存储 CPU 正在处理的数据,是一种高速缓存机制。
- 变量“类别”:全局变量、静态变量、局部变量、堆变量、常量
全局变量
存储位置:全局变量存储在全局或静态数据区。在程序启动时,这些变量就被分配了内存空间,并且在整个程序的生命周期内都存在。例如,在一个 C 语言程序中,定义在所有函数之上的变量int global_variable;就是全局变量,extern声明的变量也是全局变量且为工程静态变量。
特点:全局变量可以被程序中的任何函数访问和修改,这使得它在多个函数之间共享数据时非常有用。但是,过度使用全局变量可能会导致程序的可维护性变差,因为很难追踪是哪个函数对全局变量进行了修改。
静态变量
存储位置:静态局部变量存储在全局或静态数据区。对于静态局部变量,虽然它是在函数内部定义的,但它的生命周期和全局变量一样,贯穿整个程序的运行过程。以 C 语言为例,在函数内部定义static int static_local_variable;,这个变量就是静态局部变量。
特点:静态局部变量的作用域仅限于定义它的函数内部,这意味着其他函数无法直接访问它。每次进入函数时,静态局部变量不会重新初始化,而是保留上一次函数退出时的值。这与普通的局部变量(存储在栈中)不同,普通局部变量每次函数调用时都会重新初始化。
局部变量
存储位置**:局部变量通常存储在栈中**。当一个函数或代码块被调用时,函数内部定义的局部变量会被压入栈中。例如,在一个函数void function() {int local_variable;}中,local_variable就是局部变量。
特点:局部变量的作用域仅限于定义它的函数内部。当函数执行完毕后,栈中的这些局部变量就会被自动销毁,释放内存空间。这使得局部变量的使用相对安全,不会对其他函数的变量产生影响,除非通过指针或返回值等方式进行传递。
动态分配变量(堆变量)
存储位置:动态分配的变量存储在堆中。在 C/C++ 等语言中,通过函数如malloc(C)或new(C++)在堆上分配内存来创建变量。例如,在 C 语言中int *heap_variable = (int *)malloc(sizeof(int));就创建了一个存储在堆上的整数变量。
特点:堆变量的生命周期由程序员手动控制。需要通过相应的函数(如free(C)或delete(C++))来释放内存。这使得在处理一些不确定大小的内存需求或者需要长期保存的数据(在程序运行期间)时非常灵活,但也需要注意内存泄漏的问题,即如果忘记释放内存,会导致内存资源的浪费。
常量变量
存储位置:常量变量如果是全局常量,一般存储在常量区;如果是局部常量(例如函数内部定义的常量),在编译时可能会根据编译器优化策略,存储在代码区附近的常量池或者直接嵌入到使用它的机器指令中。例如,在 C++ 中const int global_constant = 10;(全局常量)和void function() {const int local_constant = 20;}(局部常量)。
特点:常量变量的值在程序运行期间不能被修改。对于编译器来说,常量变量可以帮助进行一些优化,比如在编译时就可以确定常量表达式的值,而不需要在运行时进行计算。
#include <iostream>
using namespace std;
int main(int argc, char *argv[])
{
// 在栈上应用指针
int number{50};
int *pNumber = &number; // 定义一个指向数组首地址的指针
// 不建议
// int *pNumber; 野指针
// *pNumber = &number;
cout << number << ": " << *pNumber << endl;
cout << &number << ": " << pNumber << endl;
// 堆上
int *pNumber0{nullptr}; // 定义一个指向整数的指针,并初始化为nullptr
pNumber0 = new int; // 通过new操作符在堆上分配一个整数的内存空间
// 这个地址就是固定的,我们就可以将这个地址传给其他函数
*pNumber0 = 13; // 将13赋值给堆上分配的整数
cout << *pNumber0 << endl; // 输出堆上分配的整数的值
delete pNumber0; // 释放堆上分配的内存
// delete pNumber0; delete两次同一片堆内存会导致程序崩溃
pNumber0 = nullptr; // 将指针重置为nullptr,避免野指针
// cout << *pNumber0 << endl; // 这行代码会导致未定义行为,因为pNumber0已经被释放,在C++中一般会输出随机数字
cout << "----- yz -----" << endl; // 输出分隔符
return 0;
}
二、指针上的错误
指针常见错误
- 指针所占的内存大小通常是8字节(在64位系统中),而在32位系统中为4字节,与存储变量类型所占字节无关
- 指针不可以指向不同的类型,不如int类型的指针获取浮点类型变量的地址
指向栈与的指针的错误
-
错误示例:
int* pNumber = nullptr; cout << *pNumber << endl;//空指针解引用
这段代码尝试访问一个未初始化的指针,会导致未定义行为
-
尽量不要这样写:
int* pNumber;//野指针 int n{13}; pNumber = &n;
这种方式虽然可以工作,但容易导致指针生命周期管理的问题
-
推荐做法:
int n{12}; int* pNumber = &n;
明确地初始化指针,并且指针指向的是一个已初始化的变量
指向堆指针的错误
- 推荐做法:
int* pNumber = new int(13);//开辟1int空间的动态内存存储13
明确地初始化了指针,并且指针指向的是一个动态分配的内存地址
-
可以重新赋值已经销毁的指针不用声明:
int* pNumber = new int(13); delete pNumber; pNumber = new int(13);
但不能直接解引用,即不能使用
*pNumber
访问已销毁的内 -
判断指针是否销毁:
可以通过检查指针是否为nullptr
来判断该指针是否已经被销毁:if (pNumber == nullptr) { // 指针已被销毁 }
内存泄漏的错误
-
忘记删除:
忘记在不再需要动态分配的内存时调用delete
,会导致内存泄漏int* pNumber = new int(13); // 忘记 delete pNumber;
-
重新指向导致内存泄漏:
如果指针重新指向新的内存地址,而没有释放旧的内存,也会导致内存泄漏int* pNumber = new int(13); pNumber = new int(25); // 旧内存未释放
-
生命周期管理:
因为指向堆上的指针在作用域结束后不会自动销毁,所以需要手动管理其生命周期,即需要手动delete
三、堆上数组和栈上数组
不推荐使用堆上数组,使用vector即可
#include <iostream>
using namespace std;
int main(int argc, char *argv[])
{
// 堆上数组:通过new动态分配内存
double *pArray(new double[13]{1, 3.1});
for (size_t i = 0; i < 13; i++)
{
cout << "index : " << i << " value : " << pArray[i] << endl;
}
// 栈上数组:在栈中分配内存
double arr[13]{123, 34, 2};
cout << "arr sizeof : " << sizeof(arr) / sizeof(arr[0]) << " p Array sizeof : " << sizeof(pArray) / sizeof(pArray[0]) << endl;
// C++11及以后版本的范围for循环
cout << "arr size : " << size(arr) << endl;
for (auto i : arr)
{
cout << i << endl;
}
// 释放堆上数组的内存
delete[] pArray;
pArray = nullptr;
if (pArray != nullptr)
{
for (size_t i = 0; i < 13; i++)
{
cout << "index : " << i << " value : " << pArray[i] << endl;
}
cout << "----- yz ----- " << endl;
}
return 0;
}
四、指针(*)与引用(&)
#include <iostream>
using namespace std;
int main()
{
// 定义一个整数变量
int value = 10;
cout << "整型变量的值: " << value << endl
<< endl;
// 指针部分
cout << "指针示例:" << endl;
int *ptr; // 声明一个未初始化的指针 (不安全)
// 初始化指针指向变量 'value'
ptr = &value;
cout << "ptr 的值: " << *ptr << endl; // 解引用指针读取数据
*ptr = 20; // 解引用指针写入数据
cout << "通过 ptr 修改后 value 的新值: " << value << endl;
// 改变指针指向另一个变量
int anotherValue = 30;
ptr = &anotherValue;
cout << "现在 ptr 指向 anotherValue: " << *ptr << endl;
// 引用部分
cout << "\n引用示例:" << endl;
int &ref = value; // 引用必须初始化,并绑定到已存在的变量上
cout << "ref 的值: " << ref << endl; // 不需要解引用操作符就可以读取数据
ref = 40; // 写入数据同样不需要解引用操作符
cout << "通过 ref 修改后 value 的新值: " << value << endl;
// 尝试改变引用指向(编译错误)
// int& ref2 = anotherValue; // 这行代码将导致编译错误,因为引用不能重新绑定
return 0;
}
五、const与指针、引用
#include <iostream>
using namespace std;
int main() {
// 定义一些变量
int value = 10;
const int constValue = 20;
// 指向非const对象的非const指针
int *ptr1 = &value;
*ptr1 = 30; // 允许修改所指向的数据
// 指向const对象的非const指针(编译错误)
// int *ptr2 = &constValue; // 不允许,因为不能通过非const指针修改const对象
// *ptr2 = 40; // 这将导致编译错误
// 指向非const对象的const指针
int * const ptr3 = &value;
// ptr3 = &constValue; // 不允许重新赋值给其他地址
*ptr3 = 50; // 允许修改所指向的数据
// 指向const对象的const指针
const int * const ptr4 = &constValue;
// ptr4 = &value; // 不允许重新赋值给其他地址
// *ptr4 = 60; // 不允许修改所指向的数据
// 引用部分
// 非const引用绑定到非const对象
int &ref1 = value;
ref1 = 70; // 允许修改所引用的对象
// const引用绑定到const对象
const int &cref2 = constValue;
// cref2 = 80; // 不允许修改所引用的对象
// const引用也可以绑定到非const对象
const int &cref3 = value;
// cref3 = 90; // 不允许修改所引用的对象,即使原对象是可变的
cout << "修改后:" << endl;
cout << "value 的值: " << value << endl; // 应该输出最后一次修改后的值 (70)
cout << "constValue 的值: " << constValue << endl; // 应该输出原始值 (20)
return 0;
}
六、函数传值passby
#include <iostream>
using namespace std;
// 按值传递
void passByValue(int x)
{
x = 10;
cout << "在 passByValue 函数内部: x = " << x << endl;
}
// 按指针传递
void passByPointer(int *ptr)
{
*ptr = 20;
cout << "在 passByPointer 函数内部: *ptr = " << *ptr << endl;
}
// 按引用传递
void passByReference(int &ref)
{
ref = 30;
cout << "在 passByReference 函数内部: ref = " << ref << endl;
}
int main()
{
int value = 5;
// 按值传递
cout << "按值传递前: value = " << value << endl;
passByValue(value);
cout << "按值传递后: value = " << value << endl
<< endl;
// 按指针传递
cout << "按指针传递前: value = " << value << endl;
passByPointer(&value);
cout << "按指针传递后: value = " << value << endl
<< endl;
// 按引用传递
cout << "按引用传递前: value = " << value << endl;
passByReference(value);
cout << "按引用传递后: value = " << value << endl
<< endl;
return 0;
}
七、函数指针与回调函数
#include <iostream>
using namespace std;
// 定义一个函数
void myFunction()
{
cout << "这是我的函数" << endl;
}
// 定义一个回调函数
void callbackFunction(void (*func)())
{
cout << "执行回调函数...";
func(); // 调用传入的函数
}
int main()
{
// 获取函数的地址并存储在函数指针中
void (*functionPointer)() = myFunction;
// 使用函数指针调用函数
cout << "使用函数指针调用函数:";
functionPointer();
// 使用回调函数
cout << "使用回调函数调用函数: ";
callbackFunction(myFunction);
return 0;
}
八、左值与右值
简单区分:左值等号右边没有变量,右值等号右边有变量或为函数返回值
#include <iostream>
using namespace std;
// 定义一个函数,返回一个临时值
int add(int a, int b)
{
return a + b;
}
int main()
{
// 定义一个变量,这是一个左值
int leftValue = 10;
// 访问左值的地址
cout << "左值的地址: " << &leftValue << endl;
// 使用左值
cout << "左值的值: " << leftValue << endl;
// 创建一个临时的右值
int rightValue = 20;
// 这里 rightValue 是一个右值
int tempRightValue = rightValue + 5;
// 上个语句 结束tempRightValue变成了左值
// 右值无法直接获取地址
// cout << "右值的地址: " << &tempRightValue << endl; // 这行代码会报错
// 使用右值
cout << "右值的值: " << tempRightValue << endl;
// 使用右值引用存储右值
int &&refRightValue = rightValue + 5; // 这里 refRightValue 是右值引用
// 使用函数返回值作为右值
int &&funcReturnValue = add(3, 4); // 这里 funcReturnValue 是右值引用
//右值引用不是值copy效率更高
// 输出结果
cout << "右值引用的值: " << refRightValue << endl;
cout << "函数返回值的值: " << funcReturnValue << endl;
return 0;
}
字符与字符串
一、字符基本操作
这些函数都定义在<cctype>头文件中
isalnum()
检查字符是否为字母或数字
isdigit()
检查字符是否为数字(0-9)
isalpha()
检查字符是否为字母(A-Z, a-z)
isspace()
检查字符是否为空白字符(包括空格、制表符、换行符等)
islower()
检查字符是否为小写字母(a-z)
toupper()
将小写字母转换为对应的大写字母。如果字符不是小写字母,则原样返回。
isupper()
检查字符是否为大写字母(A-Z)
tolower()
将大写字母转换为对应的小写字母。如果字符不是大写字母,则原样返回
#include <iostream>
#include <cctype> // 包含字符处理函数头文件
#include <string> // 包含字符串类的头文件
using namespace std;
void checkCharacterType(char ch) {
cout << "检查字符: " << ch << '\n';
// isalnum() 检查字符是否是字母或数字
if (isalnum(ch)) {
cout << "这是一个字母数字字符。\n";
// isdigit() 检查字符是否是数字(0-9)
if (isdigit(ch))
cout << "这是一个数字。\n";
// isalpha() 检查字符是否是字母(A-Z, a-z)
else if (isalpha(ch))
cout << "这是一个字母。\n";
}
// isspace() 检查字符是否为空白字符(包括空格、制表符等)
if (isspace(ch))
cout << "这是一个空白字符。\n";
}
void checkAndConvertCase(char &ch) {
// islower() 检查字符是否为小写字母(a-z)
if (islower(ch)) {
cout << "小写字母转换为大写: ";
// toupper() 将小写字母转换为对应的大写字母
ch = toupper(ch);
}
// isupper() 检查字符是否为大写字母(A-Z)
else if (isupper(ch)) {
cout << "大写字母转换为小写: ";
// tolower() 将大写字母转换为对应的小写字母
ch = tolower(ch);
}
else {
cout << "不是字母,不进行转换。\n";
}
cout << ch << '\n';
}
int main() {
// 定义一个单独的字符并对其进行检查和转换
char singleChar = 'A';
checkCharacterType(singleChar);
checkAndConvertCase(singleChar);
// 对字符串中的每个字符执行相同的操作
string str = "Hello World! 123"; // 定义一个字符串
for (char& ch : str) { // 遍历字符串中的每个字符
checkCharacterType(ch); // 检查字符类型
checkAndConvertCase(ch); // 转换字符大小写
}
// 输出转换后的字符串
cout << "大小写转换后的最终字符串: " << str << '\n';
return 0;
}
二、C风格的string
C风格的字符串在C++中是以字符数组的形式存在的,并以空字符 \0 作为字符串结束的标志。它们不像 C++ 的 std::string 类那样提供了丰富的成员函数,而是依赖于一系列标准库函数来执行字符串操作,所以不推荐使用
cstring头文件中常用函数
strlen:计算给定字符串的长度(不包括结尾的 \0)。
strcpy:将一个字符串复制到另一个字符串中。注意目标缓冲区必须有足够的空间。
strcat:将一个字符串追加到另一个字符串的末尾。同样,目标缓冲区必须有足够的空间。
strcmp:比较两个字符串。返回值为 0 表示相等,负数表示第一个字符串小于第二个,正数则相反。
strchr:在一个字符串中查找指定字符的第一个出现位置。如果找到了该字符,则返回指向它的指针;否则返回 nullptr。
strstr:在一个字符串中查找指定子串的第一个出现位置。如果找到了该子串,则返回指向它的指针;否则返回 nullptr。
#include <iostream>
#include <cstring> // 包含C风格字符串处理函数
using namespace std;
void demonstrateCStyleStrings()
{
// 定义和初始化一个C风格字符串
char str1[] = "你好";
const char *str2 = "世界"; // 也可以用指向常量字符的指针表示C风格字符串
// 使用 strlen 函数获取字符串长度
cout << "str1 的长度: " << strlen(str1) << '\n';
// 使用 strcat 函数连接两个字符串
char result[50]; // 确保结果数组足够大以容纳连接后的字符串及终止符
strcpy(result, str1); // 使用 strcpy 函数复制字符串到结果数组
strcat(result, " ");
strcat(result, str2);
cout << "连接后的字符串: " << result << '\n';
// 使用 strcmp 函数比较两个字符串
if (strcmp(str1, str2) == 0)
cout << "str1 和 str2 是相等的。\n";
else
cout << "str1 和 str2 不相等。\n";
// 使用 strchr 函数查找字符位置
char targetChar = '世';
char *pos = strchr(result, targetChar);
if (pos != nullptr)
cout << "字符 '" << targetChar << "' 在位置: " << pos - result << " 被找到。\n";
// 使用 strstr 函数查找子串位置
const char *subStr = "世界";
const char *subPos = strstr(result, subStr);
if (subPos != nullptr)
cout << "子串 '" << subStr << "' 在位置: " << subPos - result << " 被找到。\n";
}
int main()
{
// setlocale(LC_ALL, ""); // 设置本地化环境,确保正确显示中文
demonstrateCStyleStrings();
return 0;
}
三、标准string
string库中常用函数
str.size()
返回字符串的实际长度(不包括终止符)
str.capacity()
返回字符串当前分配的内存容量。这个值可能大于实际长度,因为字符串可能会预留额外的空间以减少频繁的内存重新分配
str.append(" This is an example.")
将指定的字符串追加到现有字符串的末尾
str.c_str()
返回一个指向字符串内容的常量指针,可以用于与C风格字符串交互
str.at(index)
通过索引访问字符串中的字符。如果索引超出范围,会抛出 out_of_range 异常
str.find(“World”)
查找子字符串在主字符串中的位置。如果找到,返回子字符串的起始索引;如果没有找到,返回 string::npos即为-1
#include <iostream>
#include <string>
using namespace std;
void demonstrateStringFunctions()
{
// 创建一个字符串
string str = "Hello, World!";
// 1. size 长度
cout << "字符串的长度(size): " << str.size() << endl;
// 2. capacity 容量
cout << "字符串的容量(capacity): " << str.capacity() << endl;
// 3. append 添加
str.append(" This is an example.");
cout << "添加后的字符串: " << str << endl;
// 4. c_str 与 C 风格字符串交互
const char *cStr = str.c_str();
cout << "转换为 C 风格字符串: " << cStr << endl;
// 5. at 获取字符
try
{
cout << "第 7 个字符是: " << str.at(6) << endl; // 注意索引从0开始
}
catch (const out_of_range &e)
{
cout << "错误: " << e.what() << endl;
}
// 6. find 查找子字符串
size_t found = str.find("World");
if (found != string::npos)
{
cout << "子字符串 'World' 在位置: " << found << " 被找到。" << endl;
}
else
{
cout << "子字符串 'World' 没有找到。" << endl;
}
}
int main()
{
demonstrateStringFunctions();
return 0;
}