- admin 的博客
GESP 四级
- @ 2025-11-30 20:53:50
指针
前言
计算机的内存空间中最小单位是bit(位),其值只能是0或1。字节(byte)是计算机中的基本单位,一个字节由8个bit组成。一般计量都是以字节为单位的,比如每个char类型数据占1个字节,int类型数据占4个字节,double类型数据占用8个字节。
在操作系统中,所有内存单元都被编上了序号,通过这些编号(即内存的地址),可以快速找到内存单元,更灵活、高效的管理数据。
概念
计算机中的数据都是存储在内存中的,每条数据都有自己的地址,指针就是用来记录地址的变量。 
图中 c 是一个字符型变量,值为 'K',存储在地址为 0x011A 的内存单元中。
p 是一个字符型指针变量,其值为 0x011A,也就是变量 c 的内存地址。在这种情况下,可以说 p 是一个指向变量 c 的指针。
指针的值是一个内存地址,这个地址可以是变量的首地址,也可以是数组的首地址;指针的类型表示指针所指地址上存储的数据的类型。
定义
格式:类型 *指针名;
int *p1;
double *p2;
char *p3;
使用
一、确定指针的指向
指针的值必须是一个地址,为保证它的有效性,可以借助取地址符“&”获取某个变量的地址。
int a;
int *p1 = &a;//定义指针的同时为其初始化一个地址
int *p2;
p2 = &a;//先定义个指针,需要用的时候才指定一个地址
【注意事项】
- 两种方法是等价的,不过推荐使用第一种方法,这样可以保证一开始指针就是有效的。未经初始化的指针将指向一个不确定的内存地址,操作这样的指针是非常危险的。
- 注意,在上面指针的定义中,指针变量是 p2 而不是 *p2,因此要给指针赋值,等号左边是 p2 而不是 p2。另外,不允许把一个无效的地址(比如数字)赋给指针。
指针变量和普通变量是一样的,存放的值是可以改变的,可以改变指针的指向。
int a, b;
int *p = &a; // 指针 p 指向变量 a
p = &b; // 修改 p 的指向,令它指向变量 b
二、获取指针指向的数据
需要指针所指向的数据时,可以使用解引用操作符“*”得到指针所指向的变量。
int x = 3;
int *p = &x;
cout << x << ' ' << *p << '\n';
//直接使用x和间接的访问x
x = 0;// 同 *p = 0;
//两个赋值语句是等效的
三、指针的算数运算
指针加减某个整数,是将该指针移动整数个变量的长度。
例如p + j是将指针p向后移动j个单位(每个单位大小为指针p类型的字节数),指向p原先指向元素后的第j个元素。
#include <bits/stdc++.h>
using namespace std;
int main(){
int a[5] = {1, 2, 3, 4, 5};
int *p = a;
cout << p << ' ' << *p << '\n';//0x73fdf0 1
cout << p + 2 << ' ' << *(p + 2);//0x73fdf8 3
return 0;
}
*p++ |
先获取*p的数据后再让p+1 |
|---|---|
(*p)++ |
先获取*p的数据后再让*p的数据自增1 |
*++p |
先将p自增1,再获取*p的数据 |
++*p |
先将*p的数据自增1,再获取自增后的结果 |
多维数组
声明与初始化
创建:数据类型 数组名 [行长度] [列长度];
cint arr[3][4] = {1,2,3,4};
int arr[3][4] = {{1,2},{4,5}};//其余未初始化的元素为0
int arr[][4] = {{2,3},{4,5}};
二维数组如果有初始化,行可以省略,列不能省略!!
我们可以把二维数组看成由多个一维数组组成的,只有在确定每个一维数组存放多少个元素才能确定二维数组。假设省略列,那么操作系统无法得知每个一维数组分配多少个元素,从而造成二维数组的混乱。
计算存储空间
占用空间 = 元素个数 * 一个元素所需内存大小。
| 类型名 | 存储空间 |
|---|---|
| char | 1 个字节 |
| bool | |
| short | 2 个字节 |
| float | 4 个字节 |
| int | |
| long long | 8个字节 |
| double | 8 个字节 |
使用
二维数组看似多行多列,实际上是连续存储的, 本质上还是个多个一维数组,如果把二维数组的每一行看作一个一维数组,那么每一行的一维数组也有数组名,arr[0]就是第一行的数组名,arr[1]就是第二行的数组名,以此类推。所以二维数组也可以通过下标的方式来引用每个元素。 格式:数组名[行下标][列下标];
int arr[3][4] = {{1,2},{4,5}, {7, 8}};
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 4; j++) {
cout << arr[i][j] << ' ';
}
}
下标
由于数组存储空间是连续的,那么下标也可以指距起始位置的偏移量。 数组元素的存储地址 = 数组空间首地址 + 偏移量 * 一个元素内存空间。

以行为主序的二维数组 a[m][n]a[m][n](下标从 0 开始),求 a[i]ja[i]j 对于前i−1i−1行,共有ii行,每行nn个元素,一共有i∗ni∗n个元素;对于第ii行, 之前有jj个元素; 故a[i][j]a[i][j]的存储位置相对于数组空间首地址偏移量为i∗n+ji∗n+j。

以行为主序的二维数组 a[m][n]a[m][n](下标从 1 开始),求 a[i]ja[i]j 对于前i−1i−1行,共有i−1i−1行,每行nn个元素,一共有(i−1)∗n(i−1)∗n个元素;对于第ii行,之前有j−1j−1个元素; 故a[i][j]a[i][j]的存储位置相对于数组空间首地址偏移量为(i−1)∗n+j−1(i−1)∗n+j−1。
函数
前言
“函数”将实现了某种功能,或者需要反复使用的代码包装起来形成一个功能模块,当程序中需要执行该项功能时,只需写一条语句调用实现该功能的 “函数”即可。
函数
定义格式
函数返回类型 函数名(形式参数1,形式参数2...){
函数体;
}
- 返回类型有:
int、double、char、bool、string、void或结构体类型等,其中void代表无返回值,可以不返回信息,其他类型必须在函数的最后return对应类型的信息; - 参数的书写格式为:数据类型 变量名,符合先定义后使用的规范;
return后是返回具体的一条信息,不可以是多个。
//判断函数是否为奇数
bool isodd(int x) {
return x % 2 ? true : false;
}
调用
函数名(实际参数1,实际参数2...);
- 调用函数的结果由该函数的返回值决定
- 实际参数多数情况下要保证与形式参数的个数一致
- 实际参数无需添加数据类型
int x = 3;
if (isodd(x)) {
cout << "YES";
} else {
cout << "NO";
}
传递
传值
#include <iostream>
using namespace std;
void change(int a, int b) {
int t = a;
a = b;
b = t;
cout << a << " " << b << endl;
return;
}
int main() {
原理:传值过程中实际上是复制x的值至形参a,复制y的值至形参b,在change函数内部只是让a与b的值互换,而并未使得主函数的x和y交换。
结论:传值过程中修改形参的值并不影响实参的情况!
传引用
void change(int &a, int &b) {
...;
}
在数据类型和变量名称间添加&后,使得该变量成为引用变量。引用并不是一个独立定义的变量或其他值,而是一个对已存在的变量或其他值取了一个别名。
int a = 3;
int &b = a;
cout << a << ' ' << b; //3 3
可以将别名理解为取外号,如“小狗”被取外号叫做“修勾”...
这一期间编译器不会开辟新的空间,它与其引用对象共同使用同一块空间,对别名进行操作会影响引用对象。
结论:传引用中修改形参的值会影响实参的情况!
传地址
调用函数时把主函数中实参的地址传递给函数的形参,直接对实参地址所指向的内存空间进行操作。
#include <bits/stdc++.h>
using namespace std;
void change(int *x, int *y) {
int t;
t = *x;
*x = *y;
*y = t;
cout << *x << " " << *y << endl;//5 3
return ;
}
【结论】传地址过程中修改形参发生变化会影响实参的结果!
变量作用域
全局变量
定义在函数外部的变量称为全局变量,从定义变量的位置开始到程序结束始终有效,可以为程序中所有函数共用(若在定义时若没有赋初值,其默认值为0)。
局部变量
定义在函数内的变量叫做局部变量。局部变量的存储空间是临时分配的,当函数执行完毕后,局部变量的空间就被释放。不同函数的局部变量相互独立,不能访问其他函数的局部变量。
使用原则
遵循 “就近原则” 。
#include <bits/stdc++.h>
using namespace std;
int x = 3;
void test() {
int x = 8;
cout << x << ' ';
return ;
}
int main(){
x = 5;
cout << x << ' ';
test();
cout << x << ' ';
return 0;
}
//输出结果为5 8 5
排序
冒泡排序
原理:从前往后(或从后往前)两两比较相邻元素的值,若为逆序(即a[i]>a[i + 1]),则交换它们,直到序列比较完。每一轮结束时会在待排序元素中,将最大(或最小)的元素排序至正确的位置。

//将数组a从中的n(1-n)个元素从小大升序排序
void bubble_sort(int a[], int n) {
for (int i = 1; i < n; i++) { //仅需 n - 1轮
bool flag = true;//可以提前结束
for (int j = 1; j <= n - i; j++) { //每轮仅需n - i次比较
if (a[j] > a[j + 1]) {
swap(a[j], a[j + 1]);
flag = false;
}
}
if (flag) return ;
}
return ;
}
选择排序
原理:在待排序序列中头至尾找出最小的一个关键字,与待排序序列中第一个关键字进行交换,重复此操作,最终使序列有序。

//将数组a从中的n(1-n)个元素从小大升序排序
void select_sort(int a[], int n) {
for (int i = 1; i < n; i++) {
int k = i;//最小元素的位置
for (int j = i + 1; j <= n; j++) {
if (a[j] < a[k]) {
k = j; //找出最小元素的位置
}
}
swap(a[i], a[k]);
}
return ;
}
插入排序
原理:每次将一个待排序的关键字按大小插入前面已排好的序列中,直到全部关键字插入结束。 
//将数组a从中的n(1-n)个元素从小大升序排序
void insert_sort(int a[], int n) {
for (int i = 2; i <= n; i++) {
int t = a[i], j = i - 1;
while (a[j] > t && j >= 1) { //判断是否可以插入到第j个元素之后
a[j + 1] = a[j];
j--;//继续向前比较
}
a[j + 1] = t;
}
return ;
}
【总结】
将杂乱无章的数据元素,通过一定的方法按关键字顺序排列的过程叫做排序。为了查找方便,计算机中的数据表都希望是按照关键字有序的,一般按照递增或递减方式排列。 
衡量标准
1.时间复杂度 2.空间复杂度 3.稳定性

小知识
C++的异常处理机制,通过try、catch和throw三个关键字,为开发者构建了一个结构清晰、易于理解的异常处理框架。
当程序执行到可能抛出异常的代码段时,可以使用try块将其包围起来;
随后,通过一个或多个catch块来捕获并处理可能发生的特定类型的异常;
而throw关键字则用于在程序中显式地抛出异常,通知上层调用者当前代码遇到了无法继续执行的情况
C++异常概念
传统的错误处理机制:
终止程序,如assert,缺陷:用户难以接受。如发生内存错误,除0错误时就会终止程序。 返回错误码,缺陷:需要程序员自己去查找对应的错误。
如系统的很多库的接口函数都是通过把错误码放到errno中,表示错误
异常是一种处理错误的方式,当一个函数发现自己无法处理的错误时就可以抛出异常,让函数的直接或间接的调用者处理这个错误。
throw: 当问题出现时,程序会抛出一个异常。这是通过使用 throw 关键字来完成的;
catch: 在您想要处理问题的地方,通过异常处理程序捕获异常.catch 关键字用于捕获异常,可以有多个catch进行捕;
try: try 块中的代码标识将被激活的特定异常,它后面通常跟着一个或多个 catch 块。