指针
什么是指针?
基本概念
指针就是内存地址。指针变量是存储内存地址的变量。
想象计算机内存就像一个大酒店:
- 房间号 = 内存地址(指针)
- 房间里的客人 = 存储在内存中的数据
- 房间号登记簿 = 指针变量
指针变量的定义
1 2 3 4 5 6 7 8 9 10 11 12
| 类型 *指针变量名;
#include <stdio.h>
int main() { int *p_i; char *p_c; float *p_f;
return 0; }
|
指针变量的引用
取址运算符 &
取址运算符&用来取得其操作数的地址。
1 2 3 4 5 6 7 8 9 10
| int main() { int num = 3; int* p = # printf("%d 的地址是 %p\n", num, p); return 0; }
|
解引用运算符 *
解引用运算符(间接访问运算符)*用来获取指针所指向地址的值,它返回指针所指的对象的值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| #include <stdio.h>
int main() { int num = 42; int *p = # int value = *p; printf("num = %d\n", num); printf("*p = %d\n", *p); printf("value = %d\n", value); return 0; }
|
补充说明
- & 和 * 的关系
1 2 3
| 对于任意变量 a: 1. *(&a) == a 2. &(*p) == p
|
- 优先级和结合性
&和*都是单目运算符(仅对一个操作数进行运算的运算符类型)
- 优先级:2级(较高)
- 结合性:自右向左
- &*p 等价于 &(*p)
- *&a 等价于 *(&a)
- (p_a)++与a++等价(注意,括号是必需的)。
这是因为和+的优先级别相同,都是自右向左结合,若没有括号,就相当于*(p_a++)其执行过程是先取指针变量p_a所指向的值,再使指针变量p_a的值加1(指向下一个存储单元),而不是使所指向的变量a的值加1.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| #include <stdio.h>
int main() { int a = 10; int *p = &a; printf("a=%d\n", a); (*p)++; printf("a=%d\n", a); int arr[] = {100, 200, 300}; int *q = arr; printf("\n*q=%d\n", *q); int value = *q++; printf("value=%d\n", value); printf("*q=%d\n", *q); return 0; }
|
数组
一维数组
一维数组就是一排整齐的盒子,每个盒子可以放同样类型的东西,每个盒子都有编号(从0开始)。
一维数组的定义与初始化
1 2 3 4 5 6 7 8 9 10 11 12 13
| 数据类型 数组名[大小];
int numbers[5] = {1, 2, 3, 4, 5};
int nums[5] = {1, 2, 3};
int days[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
int zeros[10] = {0};
|
一维数组的引用
下标引用法
1 2 3 4 5 6 7 8 9
| int arr[5] = {10, 20, 30, 40, 50};
int a = arr[0]; int b = arr[2];
arr[1] = 25; arr[3] = arr[2] * 2;
|
指针引用法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| int arr[5] = {1, 2, 3, 4, 5};
int *p = arr; int x = *p; int y = *(p + 2);
int z = *(arr + 3);
int *p = arr; printf("%d ", *p); p++; printf("%d ", *p);
|
注:arr 本身就是一个指针,可以直接赋值给指针变量 p。arr 是数组第 0 个元素的地址,所以int *p = arr;也可以写作int *p = &arr[0];。也就是说,arr、p、&arr[0] 这三种写法都是等价的,它们都指向数组第 0 个元素,或者说指向数组的开头。
指针法示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| #include <stdio.h>
int main() { int i; int a[10]; int *p = a;
for (i = 0; i < 10; i++) {
scanf("%d", p++); } p = a; for (i = 0; i < 10; i++) { printf("%d ", *p++); }
return 0; }
|
重要指针运算表达式
假设 p 是指向数组 arr 中第 n 个元素的指针,那么 *p++、*++p、(*p)++ 分别是什么意思呢?
*p++ 等价于 *(p++),表示先取得第 n 个元素的值,再将 p 指向下一个元素。
*++p 等价于 *(++p),会先进行 ++p 运算,使得 p 的值增加,指向下一个元素,整体上相当于 *(p+1),所以会获得第 n+1 个数组元素的值。
(*p)++ 就非常简单了,会先取得第 n 个元素的值,再对该元素的值加 1。假设 p 指向第 0 个元素,并且第 0 个元素的值为 99,执行完该语句后,第 0 个元素的值就会变为 100。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| 示例:
#include <stdio.h>
int main() { int arr[] = {1, 2, 3, 4, 5}; int *p; p = &arr[2]; int a = *p++; p = &arr[2]; int b = *++p; p = &arr[2]; int c = (*p)++; p = &arr[2]; int d = ++(*p); return 0; }
|
二维数组
二维数组的定义与初始化
二维数组是一个数组的数组。一个二维数组,在本质上是由多个一维数组构成。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| 数据类型 数组名[行数][列数];
#include <stdio.h>
int main() { int matrix1[3][4] = { {1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12} }; int matrix2[3][4] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; int matrix3[3][4] = { {1, 2}, {5, 6, 7}, {9} }; int matrix4[][4] = { {1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12} }; int matrix5[3][4] = {0}; return 0; }
|
二维数组的引用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| 数组名[行下标][列下标]
#include <stdio.h> #define N 3 #define M 4 int main() { int a[N][M], i, j; printf("Input 3*4 data for the array:\n"); for(i=0; i<N; i++) for(j=0; j<M; j++) scanf("%d", &a[i][j]); printf("Output the array:\n"); for(i=0; i<N; i++) { for(j=0; j<M; j++) printf("%5d", a[i][j]); printf("\n"); } return 0; }
|
1 2 3 4 5
| 输入1 2 3 4 5 6 7 8 9 10 11 12后,输出为: Output the array: 1 2 3 4 5 6 7 8 9 10 11 12
|
二维数组的指针
在 C 语言中,二维数组本质是 “数组的数组”—— 它在内存中连续存储。二维数组的地址分两种类型:行地址(指向整行) 和 列地址(指向单个元素)。
列指针
列指针本质是指向 “单个元素” 的普通指针,对应二维数组的 “列地址”,利用二维数组在内存中 “连续存储” 的特性,可将二维数组当作 “超长一维数组” 遍历。
什么是列指针?
列指针是指向数组单个元素的普通指针(比如int *p),对应二维数组的 “列地址”:
- 它指向的是二维数组中的某一个具体元素;
- 列指针+1会直接跳转到当前行的下一个元素(仅偏移 1 个元素,而非整行)。
列指针的定义与用法
列指针的定义是普通指针的写法,无需额外语法,直接指向二维数组的首元素即可。示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| #include <stdio.h>
int main() { int a[3][4] = {{1,2,3,4}, {5,6,7,8}, {9,10,11,12}}; int *p = &a[0][0];
printf("二维数组的元素输出:\n"); for(int i = 0; i < 12; i++) { printf("%5d", *p++); if((i + 1) % 4 == 0) { printf("\n"); } }
return 0; }
|
输出:
1 2 3 4
| 二维数组的元素输出: 1 2 3 4 5 6 7 8 9 10 11 12
|
列指针的等价访问方式
由于二维数组内存连续,列指针可通过 “一维下标” 访问任意元素(需结合列数计算位置),等价写法如下(以列数为 4 为例):
1 2
| *(p + i*4 + j) == p[i*4 + j] == a[i][j]
|
行指针
行指针对应二维数组的 “行地址”,能直接按 “行 - 列” 结构访问数组,比列指针更贴合二维数组的逻辑结构。
什么是行指针?
行指针是指向 “固定长度一维数组” 的指针,对应二维数组的 “行地址”:
- 它指向的是 “由 m 个元素组成的一维数组”(m 是二维数组的列数);
- 行指针+1会直接跳过一整行(即 m 个元素),比如指向含 4 个 int 的数组时,p+1会跳过 4 个 int,直接到下一行首地址。
行指针的定义与用法
例如,定义一个指向 “含 4 个 int 的一维数组” 的行指针:
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| #include <stdio.h>
int main() { int a[3][4] = {{1,2,3,4}, {5,6,7,8}, {9,10,11,12}}; int (*p)[4]; p = a;
printf("二维数组的元素输出:\n"); for(int i = 0; i < 3; i++) { for(int j = 0; j < 4; j++) { printf("%5d", p[i][j]); } printf("\n"); }
return 0; }
|
输出:
1 2 3 4
| 二维数组的元素输出: 1 2 3 4 5 6 7 8 9 10 11 12
|
二维数组元素的等价访问方式
行指针访问二维数组元素时,存在多种等价写法(以p指向a为例),本质都是通过 “行地址 + 列偏移” 定位元素:
1 2 3 4 5
| p[i][j] == *(p[i] + j) == *(*(p + i) + j) == a[i][j]
|
二维数组的地址含义(以int a[3][4]为例)
| 表示形式 |
含义 |
说明(地址类型/特性) |
a |
二维数组名;数组首地址;第0行的首地址 |
行地址,+1 跳转至下一行首地址(偏移4个int) |
a+1 / &a[1] |
第1行的首地址 |
行地址,+1 跳转至第2行首地址 |
a[0] / *(a+0) / *a / &a[0][0] |
第0行首地址;第0行第0列元素的地址 |
列地址,+1 跳转至当前行下一个元素地址(偏移1个int) |
a[1]+2 / *(a+1)+2 / &a[1][2] |
第1行第2列元素a[1][2]的地址 |
列地址,指向具体元素的地址 |
*(a[1]+2) / *(*(a+1)+2) / a[1][2] |
第1行第2列元素a[1][2]的值 |
整型值,无地址属性 |
字符数组和指针
字符数组是 C 语言中存储字符 / 字符串的核心工具,它既可以存储单个字符的集合,也能通过\0(字符串结束符)存储完整字符串。
字符数组的定义
1 2 3 4 5
| char 数组名[n];
char arr[10];
|
字符数组初始化方式
| 初始化方式 |
示例 |
说明(是否含\0) |
逐个字符初始化(无\0) |
char arr1[5] = {'h','e','l','l','o'}; |
纯字符数组,长度5,无\0,不能当作字符串使用 |
逐个字符初始化(加\0) |
char arr2[6] = {'h','e','l','l','o','\0'}; |
字符串数组,长度6,手动加\0 |
| 字符串常量初始化(推荐) |
char arr3[6] = "hello"; |
等价于arr2,编译器自动在末尾加\0 |
| 省略长度初始化 |
char arr4[] = "hello"; |
长度自动为6(5个字符+1个\0),最简洁 |
| 部分初始化 |
char arr5[10] = "hi"; |
前2位为'h'、'i',剩余位置自动补\0 |
字符数组的输入输出
输入
| 方式 |
示例 |
特点/注意事项 |
| scanf(“%s”, arr) |
scanf("%s", arr); |
不读取空格/换行,自动加\0;注意数组长度,避免溢出 |
| gets() |
gets(arr); |
读取空格/换行,自动加\0;存在缓冲区溢出风险 |
| fgets() |
fgets(arr, 10, stdin); |
读取空格/换行,指定最大读取长度;会保留换行符,需手动处理 |
输出
| 方式 |
示例 |
适用场景 |
| 单个字符输出 |
printf("%c", arr[i]); |
逐个输出字符 |
| 整串输出 |
printf("%s", arr); |
含\0的字符串数组 |
| 整串输出(puts()) |
puts(arr); |
输出字符串并自动换行 |
字符串处理函数(需包含<string.h>)
| 函数名 |
功能 |
示例 |
返回值/说明 |
| strlen() |
计算字符串长度(不含\0) |
strlen("hello") |
返回5(仅统计有效字符,不含结束符) |
| strcpy() |
字符串拷贝 |
strcpy(arr, "hello") |
将”hello”拷贝到arr,需保证arr长度足够 |
| strcat() |
字符串拼接 |
strcat(arr, " world") |
将” world”拼接到arr末尾,需保证arr长度足够 |
| strcmp() |
字符串比较(按ASCII) |
strcmp("a", "b") |
a<b返回-1,a==b返回0,a>b返回1 |
| strlwr() |
大写字母转小写字母 |
strlwr("Abcd") |
将”Abcd”转为”abcd” |
| strupr() |
小写字母转大写字母 |
strupr("Abcd") |
将”Abcd”转为”ABCD” |
多级指针和指针数组
多级指针
多级指针的本质是指针变量,只是它指向的不是普通变量,而是另一个指针变量的地址。即指向指针的指针
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13
| #include <stdio.h>
int main() { char *str_arr[3] = {"C语言", "指针数组", "多级指针"}; char **p = str_arr; for (int i = 0; i < 3; i++) { printf("第%d个字符串:%s\n", i+1, *p); p++; } return 0; }
|
输出:
1 2 3
| 第1个字符串:C语言 第2个字符串:指针数组 第3个字符串:多级指针
|
多级指针的引用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| #include <stdio.h> int main() { int x; int *p = &x; int **q = &p; int ***t = &q;
x = 10; *p = 1; **q = 2; ***t = 3;
printf("x = %d\n", x); printf("*p = %d\n", *p); printf("**q = %d\n", **q); printf("***t = %d\n", ***t); return 0; }
|
指针数组
指针数组的本质是数组(不是指针),只是数组的每个元素都是同类型的指针变量
- 普通数组:元素是
int/char等基础类型(如int arr[5],元素是 5 个int);
- 指针数组:元素是
int*/char*等指针类型(如int *p[5],元素是 5 个int*)。
指针数组的定义与用法
例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| #include<stdio.h> #include<string.h> #define N 100 int main() { char country[N][20], *p[N]; char *t; int i,j,k,n; for(i=0;i<N;i++) p[i] = country[i]; printf("Input n:"); scanf("%d", &n); printf("Input country name:\n"); for(i=0;i<n;i++) scanf("%s", p[i]); for(i=0;i<n-1;i++) { k = i; for(j=i+1;j<n;j++) { if(strcmp(p[k], p[j]) > 0) k = j; } if(k!=i) { t = p[k]; p[k] = p[i]; p[i] = t; } } printf("Sorted country name:\n"); for(i=0;i<n;i++) printf("%s ", p[i]); return 0; }
|