# 7 指针🎄
# 7.1 概述🌳
# 7.1.1 内存🌲
内存含义:
- 存储器:计算机的组成中,用来存储程序和数据,辅助 CPU 进行运算处理的重要部分
- 内存:内部存储器,暂存程序 / 数据 —— 断电丢失 SRAM,DRAM,DDR,DDR2,DDR3
- 外存:外部存储器,长时间保存程序 / 数据 —— 断点不丢失 ROM ,ERROM,FLASH (NAND,NOR),硬盘,光盘。
内存是沟通 CPU 与硬盘的桥梁:
- 暂存放,CPU 中的运算数据
- 暂存放,与硬盘等外部存储器交换的数据
# 7.1.2 物理存储器和存储地址空间🌲
有关内存的两个概念:物理存储器和存储地址空间.
物理存储器:实际存在的具体存储器芯片
- 主板上装插的内存条
- 显示卡上的显示 RAM 芯片
- 各种适配卡上的 RAM 芯片和 ROM 芯片
存储地址空间:对存储器编码的范围。我们在软件上常说的内存是指这一层含义
- 编码:对每个物理存储单元 (一个字节) 分配一个号码
- 寻址:可以根据分配的号码找到对应的存储单元,完成数据的读写
# 7.1.3 内存地址🌲
- 将内存抽象成一个很大的一维字符数组
- 编码就是对内存的每一个字节分配一个 32 位或 64 位的编号 (与 32 位或者 64 位处理器相关)
- 这个内存编号我们称之为内存地址。
内存中的每一个数据都会分配一个地址:
- char:占一个字节分配一个地址
- int:占四个字节分配四个地址
- float,struct,函数,数组等
编写一段代码:
| #include <stdio.h> |
| |
| int main(void) |
| { |
| |
| int a = 0xaabbccdd; |
| printf("%p\n", &a); |
| getchar(); |
| return 0; |
| } |
在 getchar () 位置打断点然后进行 debug,从终端复制一下 a 变量的地址,到调试的内存中进行查询
当我们对这个内存地址进行一下修改访问 000000F0470FF7B5 呢
访问 000000F0470FF7B6 呢
终端输出的是 a 变量内存地址的首地址,一个 int 类型 4 个字节 8 个比特。首地址与其它的三个内存地址是连续的一个内存空间
# 7.1.4 指针和指针变量🌲
- <font color='red'> 内存区的每一个字节都有一个编号,这就是 “地址”</font>.
- 如果在程序中定义了一个变量,在对程序进行编译或运行时,系统就会给这个变量分配内存单元,并确定它的内存地址 (编号)
- 指针的实质就是内存 “地址” ,指针就是地址,地址就是指针
- <font color='red'> 指针是内存单元的编号,指针变量是存放地址的变量 </font>.
- 通常我们叙述时会把指针变量简称为指针,实际它们含义并不一样
# 7.2 指针的基础知识🌳
# 7.2.1 指针变量的定义和使用🌲
- 指针也是一种数据类型,指针变量也是一种变量.
- 指针变量指向谁,就把谁的地址赋值给指针变量
- ‘*’ 操作符操作的是指针变量指向的内存空间
| #include <stdio.h> |
| |
| int main(void) |
| { |
| int a = 10; |
| |
| int* p; |
| |
| p = &a; |
| printf("%p\n", &a); |
| printf("%p\n", p); |
| printf("----------------\n"); |
| printf("%d\n", a); |
| printf("%d\n", *p); |
| return 0; |
| } |
| |
| 000000A2548FFCB4 |
| 000000A2548FFCB4 |
| ---------------- |
| 10 |
| 10 |
| |
| E:\C\Demo\Project3\指针\x64\Debug\指针.exe (进程 18928)已退出,代码为 0。 |
| 按任意键关闭此窗口. . . |
# 7.2.2 通过指针间接修改变量的值🌲
| #include <stdio.h> |
| |
| int main(void) |
| { |
| int a = 10; |
| |
| int* p; |
| |
| p = &a; |
| |
| *p = 100; |
| printf("%d\n", a); |
| printf("%d\n", *p); |
| return 0; |
| } |
| |
| 100 |
| 100 |
| |
| E:\C\Demo\Project3\指针\Debug\指针.exe (进程 15900)已退出,代码为 0。 |
| 按任意键关闭此窗口. . . |
# 7.2.3 指针大小🌲
- <font color='red'> 使用 sizeof () 测量指针的大小,得到的总是:4 或 8</font>.
- sizeof () 测的是指针变量指向存储地址的大小
- 在 32 位平台,所有的指针 (地址) 都是 32 位 (4 字节)
- 在 64 位平台, 所有的指针 (地址) 都是 64 位 (8 字节)
| int main(void) |
| { |
| |
| printf("int %d\n", sizeof(int*)); |
| printf("char %d\n", sizeof(char*)); |
| printf("short %d\n", sizeof(short*)); |
| printf("long %d\n", sizeof(long*)); |
| printf("long long %d\n", sizeof(long long*)); |
| printf("float %d\n", sizeof(float*)); |
| printf("double %d\n", sizeof(double*)); |
| return 0; |
| } |
| |
| int 4 |
| char 4 |
| short 4 |
| long 4 |
| long long 4 |
| float 4 |
| double 4 |
| |
| E:\C\Demo\Project3\指针\Debug\指针.exe (进程 17352)已退出,代码为 0。 |
| 按任意键关闭此窗口. . . |
# 7.2.4 强制类型转换🌲
| #include <stdio.h> |
| |
| int main(void) |
| { |
| int a = 10; |
| |
| |
| |
| int p = &a; |
| |
| *(int*)p = a; |
| return 0; |
| } |
# 7.2.5 指针类型与变量类型🌲
| #include <stdio.h> |
| |
| int main(void) |
| { |
| char ch = 'a'; |
| int* p = &ch; |
| printf("--------------------内存地址--------------------\n"); |
| printf("%p\n", ch); |
| printf("%p\n", p); |
| printf("------------------------------------------------\n"); |
| |
| |
| |
| printf("%d\n", ch); |
| printf("%d\n", *p); |
| return 0; |
| } |
| |
| --------------------内存地址-------------------- |
| 00000061 |
| 0113FCD7 |
| ------------------------------------------------ |
| 97 |
| -858993567 |
| |
| E:\C\Demo\Project3\指针\Debug\指针.exe (进程 12412)已退出,代码为 0。 |
| 按任意键关闭此窗口. . . |
# 7.2.6 野指针和空指针🌲
指针变量也是变量,是变量就可以任意赋值,不要越界即可 (32 位为 4 字节,64 位为 8 字节),但是,<font color='red'> 任意数值赋值给指针变量没有意义,因为这样的指针就成了野指针 </font>,此指针指向的区域是未知 (操作系统不允许此指针指向的内存区域)。所以,<font color='red'> 野指针不会直接引起错误,操作野指针的内存区域才会出问题 </font>。
| #include <stdio.h> |
| |
| int main(void) |
| { |
| |
| |
| |
| int* p = 255; |
| |
| printf("p = %d\n", *p); |
| return 0; |
| } |
| |
| 啥也没有 |
查看内存情况 debug
这块地址是不允许访问的
但是,野指针和有效指针变量保存的都是数值,为了标志此指针变量没有指向任何变量 (空闲可用) ,C 语言中,可以把 NULL 赋值给此指针,这样就标志此指针为空指针,没有任何指针。
| #include <stdio.h> |
| |
| int main(void) |
| { |
| |
| |
| int* p = NULL; |
| *p = 100; |
| printf("%d\n", *p); |
| return 0; |
| } |
NULL 是一个值为 0 的宏常量:
# 7.2.7 万能指针 void*🌲
void* 指针可以指向任意变量的内存空间:
| #include <stdio.h> |
| |
| int main(void) |
| { |
| int a = 100; |
| |
| |
| void* v = &a; |
| |
| *(int*)v = 200; |
| printf("a = %d\n", a); |
| |
| printf("*(int*)v = %d\n", *(int*)v); |
| printf("v = %d\n", v); |
| |
| printf("sizeof(v)万能指针占内存大小:%d\n", sizeof(v)); |
| return 0; |
| } |
| |
| a = 200 |
| *(int*)v = 200 |
| v = 5897636 |
| sizeof(v)万能指针占内存大小:4 |
| |
| E:\C\Demo\Project3\指针\Debug\指针.exe (进程 13120)已退出,代码为 0。 |
| 按任意键关闭此窗口. . . |
将其 void* 类型指针赋值给其它任何类型的指针不需要强制类型转换
| #include <stdio.h> |
| |
| int main(void) |
| { |
| int a = 10; |
| void* p = &a; |
| int* p1 = p; |
| printf("%d\n", *p1); |
| return 0; |
| } |
| |
| 10 |
| |
| E:\C\Demo\Project3\指针\Debug\指针.exe (进程 6992)已退出,代码为 0。 |
| 按任意键关闭此窗口. . . |
# 7.2.8 const 修饰的指针变量🌲
在编辑程序时,指针作为函数参数,如果不想修改指针对应内存空间的值,需要使用 const 修饰指针数据类型。
基本语法:
| const int* p = &a; |
| |
| int* const p = &a; |
# const 修饰的指针 到底是约束了指针自己还是内存空间呢?🎋
# 7.2.8.1 常量指针🌴
const int* p = &a;
| #include <stdio.h> |
| |
| int main(void) |
| { |
| int a = 10; |
| |
| const int* p = &a; |
| |
| |
| |
| *p = 20; |
| printf("变量a的值:%d\n", a); |
| printf("常量指针存储的值:%d\n", *p); |
| printf("常量指针内存地址:%p\n", p); |
| return 0; |
| } |
# 7.2.8.2 指针常量🌴
int* const p = &a;
| #include <stdio.h> |
| |
| int main(void) |
| { |
| int a = 10; |
| |
| int* const p = &a; |
| |
| p = 20; |
| |
| |
| printf("变量a的值:%d\n", a); |
| printf("常量指针存储的值:%d\n", *p); |
| printf("常量指针内存地址:%p\n", p); |
| return 0; |
| } |
# 7.2.8.3 常量指针常量🌴
const int* const p = &a;
指针类型和变量都被 const 修饰了就不能直接使用 p 或者 * p 来进行修改了否则报错。
| #include <stdio.h> |
| |
| int main(void) |
| { |
| int a = 10; |
| |
| |
| const int* const p = &a; |
| |
| p = 20; |
| |
| *p = 20; |
| return 0; |
| } |
# 7.2.8.4 通过二级指针修改常量指针常量🌴
那还能不能修改呢?可以的!普通的常量修饰的变量我们可以使用一级指针进行修改,常量修饰的一级指针我们可以使用二级指针进行修改:
| #include <stdio.h> |
| |
| int main(void) |
| { |
| int a = 10; |
| int b = 20; |
| |
| |
| const int* const p = &a; |
| |
| |
| |
| |
| |
| int** p1 = &p; |
| |
| **p1 = 123; |
| printf("%p\n", **p1); |
| printf("%p\n", &p); |
| printf("%p\n", p); |
| printf("%d\n", *p); |
| printf("%d\n", a); |
| printf("%d\n", b); |
| |
| *p1 = &b; |
| printf("*p1 = &b:%d\n", *p); |
| printf("*p1 = &b:%d\n", a); |
| |
| |
| **p1 = 123; |
| |
| printf("*p1 = &b:-> **p1 = 123:%d\n", *p); |
| |
| printf("*p1 = &b:-> **p1 = 123:%d\n", a); |
| return 0; |
| } |
| |
| 0000007B |
| 00B3FC38 |
| 00B3FC50 |
| 123 |
| 123 |
| 20 |
| *p1 = &b:20 |
| *p1 = &b:123 |
| *p1 = &b:-> **p1 = 123:123 |
| *p1 = &b:-> **p1 = 123:123 |
| |
| E:\C\Demo\Project3\指针\Debug\指针.exe (进程 19116)已退出,代码为 0。 |
| 按任意键关闭此窗口. . . |
# 7.2.9 指针修改常量的值🌲
| #include <stdio.h> |
| |
| int main(void) |
| { |
| |
| const int a = 10; |
| |
| |
| int* p = &a; |
| *p = 100; |
| printf("%d\n", a); |
| return 0; |
| } |
| |
| 100 |
| |
| E:\C\Demo\Project3\指针\Debug\指针.exe (进程 15728)已退出,代码为 0。 |
| 按任意键关闭此窗口. . . |
# 7.3 指针和数组🌳
# 7.3.1 数组名🌲
数组名字是数组的首元素地址,但它是一个常量:
| int arr[] = {1, 2, 3, 4}; |
| printf("a = %p\n", a); |
| printf("a[0]内存地址 = %p\n", &a[0]); |
| a = 10; |
# 7.3.2 指针操作数组元素🌲
| #include <stdio.h> |
| |
| int main(void) |
| { |
| |
| |
| int arr[] = {1231, 2, 3, 4, 5, 6, 7, 8, 'a', 'b', 'c'}; |
| |
| |
| |
| |
| int* p = arr; |
| *p = 123; |
| |
| printf("%d\n", sizeof(arr)); |
| |
| printf("%d\n", sizeof(p)); |
| printf("arr = %p\n", arr); |
| printf("p = %p\n", p); |
| printf("*p = %d\n", *p); |
| |
| |
| printf("*(p + 1) = %d\n", *(p + 1)); |
| int i = 0; |
| for (;i < sizeof(arr)/ sizeof(arr[0]);i++) |
| { |
| |
| printf("arr[%d] = %d\t", i, *p++); |
| } |
| printf("\n"); |
| |
| |
| int step = p - arr; |
| printf("p - arr = %d\n", step); |
| return 0; |
| } |
| |
| 44 |
| 4 |
| arr = 00DAF9D0 |
| p = 00DAF9D0 |
| *p = 123 |
| *(p + 1) = 2 |
| arr[0] = 123 arr[1] = 2 arr[2] = 3 arr[3] = 4 arr[4] = 5 arr[5] = 6 arr[6] = 7 arr[7] = 8 arr[8] = 97 arr[9] = 98 arr[10] = 99 |
| p - arr = 11 |
| |
| E:\C\Demo\Project3\指针\Debug\指针.exe (进程 6372)已退出,代码为 0。 |
| 按任意键关闭此窗口. . . |
# 7.3.3 数组当参数传入退化为指针🌲
| #include <stdio.h> |
| |
| extern void sort(int); |
| |
| int main(void) |
| { |
| int arr[] = {1, 2, 3, 4, 5, 6, 7, 8, 9}; |
| sort(arr); |
| return 0; |
| } |
| |
| void sort(int arr[]) |
| { |
| |
| |
| printf("sizeof(arr)/ sizeof(arr[0]):%d\n", sizeof(arr)/ sizeof(arr[0])); |
| printf("sizeof(arr):%d\n", sizeof(arr)); |
| } |
| |
| sizeof(arr)/ sizeof(arr[0]):1 |
| sizeof(arr):4 |
| |
| E:\C\Demo\Project3\指针\Debug\指针.exe (进程 7912)已退出,代码为 0。 |
| 按任意键关闭此窗口. . . |
# 7.3.3.1 指针方式编写冒泡排序🌴
| #include <stdio.h> |
| |
| extern void sort1(int); |
| |
| int main(void) |
| { |
| int arr[] = {-1, 2, 3, 1, -2, -3, 5, 4, 6, 8, 9, 7}; |
| sort1(arr, sizeof(arr)/ sizeof(arr[0])); |
| int* p = arr; |
| int i = 0; |
| for (;i < sizeof(arr)/ sizeof(arr[0]);i++) |
| { |
| printf("arr[%d] = %d\t", i, *(p + i)); |
| } |
| return 0; |
| } |
| |
| void sort1(int* arr, int len) |
| { |
| int flag = 1; |
| int i = 0; |
| for (;i < len - 1;i++) |
| { |
| int j = 0; |
| for (;j < len - i - 1;j++) |
| { |
| if (*(arr + j) > *(arr + j + 1)) |
| { |
| flag = 0; |
| int temp = *(arr + j + 1); |
| *(arr + j + 1) = *(arr + j); |
| *(arr + j) = temp; |
| } |
| } |
| if (flag) |
| { |
| break; |
| } |
| else |
| { |
| flag = 1; |
| } |
| } |
| } |
| |
| arr[0] = -3 arr[1] = -2 arr[2] = -1 arr[3] = 1 arr[4] = 2 arr[5] = 3 arr[6] = 4 arr[7] = 5 arr[8] = 6 arr[9] = 7 arr[10] = 8 arr[11] = 9 |
| E:\C\Demo\Project3\指针\Debug\指针.exe (进程 20376)已退出,代码为 0。 |
| 按任意键关闭此窗口. . . |
# 7.3.4 指针加减运算🌲
# 7.3.4.1 加法运算🌴
- <font color='red'> 指针计算不是简单的整型相加 </font>.
- 如果是一个 int* ,+1 的结果是增加一个 int (4 个字节) 的大小
- 如果是一个 char* ,+1 的结果是增加一个 char (一个字节) 的大小
| #include <stdio.h> |
| |
| int main(void) |
| { |
| int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; |
| int* p = &arr[-3]; |
| printf("%p\n", p); |
| printf("%p\n", arr); |
| |
| |
| |
| p++; |
| p++; |
| p++; |
| |
| printf("%p\n", p); |
| printf("%p\n", arr); |
| return 0; |
| } |
| |
| 003CFE0C |
| 003CFE18 |
| 003CFE18 |
| 003CFE18 |
| |
| E:\C\Demo\Project3\指针\Debug\指针.exe (进程 16040)已退出,代码为 0。 |
| 按任意键关闭此窗口. . . |
# 7.3.4.2 减法运算🌴
- <font color='red'> 指针计算不是简单的整型相减 </font>.
- 如果是一个 int* ,-1 的结果是减少一个 int (4 个字节) 的大小
- 如果是一个 char* ,-1 的结果是减少一个 char (一个字节) 的大小
| int main(void) |
| { |
| int arr[] = {1, 2, 3, 4, 5, 6, 7, 8, 9}; |
| int* p = &arr[3]; |
| printf("%p\n", p); |
| printf("%p\n", arr); |
| |
| int step = p - arr; |
| printf("step = %d\n", step); |
| printf("arr[-1] = %d\n", arr[-1]); |
| |
| printf("p[-2] = %d\n", p[-2]); |
| |
| p--; |
| p--; |
| p--; |
| printf("%p\n", p); |
| printf("%p\n", arr); |
| |
| int stepp = p - arr; |
| printf("偏移量消除后step = %d\n", stepp); |
| |
| printf("------------------------------\n"); |
| |
| int arr1[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; |
| char* p1 = arr; |
| p1 = &arr1[3]; |
| printf("%p\n", p1); |
| printf("%p\n", arr1); |
| |
| int step1 = p1 - arr1; |
| printf("%d\n", step1); |
| p1--; |
| p1--; |
| p1--; |
| printf("%p\n", p1); |
| printf("%p\n", arr1); |
| |
| int stepp1 = p1 - arr1; |
| printf("偏移量消除后step = %d\n", stepp1); |
| return 0; |
| } |
| |
| 010FFA90 |
| 010FFA84 |
| step = 3 |
| arr[-1] = -858993460 |
| p[-2] = 2 |
| 010FFA84 |
| 010FFA84 |
| 偏移量消除后step = 0 |
| ------------------------------ |
| 010FFA40 |
| 010FFA34 |
| 12 |
| 010FFA3D |
| 010FFA34 |
| 偏移量消除后step = 9 |
| |
| E:\C\Demo\Project3\指针\Debug\指针.exe (进程 4800)已退出,代码为 0。 |
| 按任意键关闭此窗口. . . |
# 7.3.4.2 字符串的拷贝逐步简化版本🌲
| #include <stdio.h> |
| |
| extern void my_strcopy(char, char); |
| |
| extern void my_strcopy2(char, char); |
| |
| extern void my_strcopy3(char, char); |
| |
| int main(void) |
| { |
| |
| |
| char ch[] = "hello world !"; |
| char dest[100]; |
| my_strcopy(dest, ch); |
| printf("my_strcopy:%s\n", dest); |
| |
| char ch1[] = "hello world !"; |
| char dest1[100]; |
| my_strcopy2(dest1, ch1); |
| printf("my_strcopy2:%s\n", dest); |
| |
| char ch2[] = "hello world !"; |
| char dest2[100]; |
| my_strcopy3(dest2, ch2); |
| printf("my_strcopy3:%s\n", dest); |
| return 0; |
| } |
| |
| void my_strcopy(char* dest, char* ch) |
| { |
| int i = 0; |
| |
| while (*(ch + i)) |
| { |
| *(dest + i) = *(ch + i); |
| i++; |
| } |
| *(dest + i) = 0; |
| } |
| |
| void my_strcopy2(char* dest, char* ch) |
| { |
| while (*ch) |
| { |
| *dest = *ch; |
| dest++; |
| ch++; |
| } |
| *dest = 0; |
| } |
| |
| void my_strcopy3(char* dest, char* ch) |
| { |
| while (*(dest++) = *(ch++)); |
| |
| * 第一步: 先 * ch *dest 取出值 |
| * 第二步: 执行 = 将 * ch 的值赋值给 *dest |
| * -> 第三步: 进行 while 的条件判断,先赋值后判断 ,字符串的最后赋值 0 判断结束循环 |
| * 第四步: 执行 dest++ ,ch++ |
| */ |
| } |
| |
| my_strcopy:hello world ! |
| my_strcopy2:hello world ! |
| my_strcopy3:hello world ! |
| |
| E:\C\Demo\Project3\指针\Debug\指针.exe (进程 18628)已退出,代码为 0。 |
| 按任意键关闭此窗口. . . |
# 7.3.5 指针和运算符的操作🌲
指针不能使用:加,乘,除,取余 可以使用 减,和其它的逻辑运算符和比较运算符
| #include <stdio.h> |
| |
| |
| int main(void) |
| { |
| int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; |
| int* p = &arr[3]; |
| |
| |
| |
| |
| |
| if (p > arr) |
| { |
| printf("真\n"); |
| } |
| else |
| { |
| printf("假\n"); |
| } |
| int* p1 = 100; |
| int res = p1 - arr; |
| |
| printf("%d\n", res); |
| return 0; |
| } |
| |
| 真 |
| -1310548 |
| |
| E:\C\Demo\Project3\指针\Debug\指针.exe (进程 19912)已退出,代码为 0。 |
| 按任意键关闭此窗口. . . |
# 7.3.6 指针数组🌲
指针数组,它是数组,数组的每个元素都是指针类型
| #include <stdio.h> |
| |
| int main(void) |
| { |
| |
| |
| int a = 10; |
| int b = 20; |
| int c = 30; |
| int* arr[] = {&a, &b, &c}; |
| printf("%d\n", *arr[0]); |
| printf("%d\n", sizeof(arr)); |
| printf("%d\n", sizeof(*arr)); |
| printf("%d\n", sizeof(*arr[0])); |
| |
| printf("%d\n", sizeof(arr[0])); |
| int i = 0; |
| for (;i < sizeof(arr)/ sizeof(*arr[0]);i++) |
| { |
| printf("arr[%d] = %d\n", i, *arr[i]); |
| } |
| return 0; |
| } |
| |
| 10 |
| 12 |
| 4 |
| 4 |
| 4 |
| arr[0] = 10 |
| arr[1] = 20 |
| arr[2] = 30 |
| |
| E:\C\Demo\Project3\指针\Debug\指针.exe (进程 19344)已退出,代码为 0。 |
| 按任意键关闭此窗口. . . |
另一种格式
| #include <stdio.h> |
| |
| int main(void) |
| { |
| |
| int a[] = { 1, 2, 3 }; |
| int b[] = { 4, 5, 6 }; |
| int c[] = { 7, 8, 9 }; |
| |
| int* arr[] = { a, b, c }; |
| int** p = arr; |
| |
| printf("%p\n", arr); |
| printf("%p\n", &arr[0]); |
| printf("%p\n", arr[0]); |
| printf("%p\n", a); |
| int i = 0; |
| for (;i < sizeof(arr)/ sizeof(*arr[0]); i++) |
| { |
| int j = 0; |
| for (;j < sizeof(arr)/ sizeof(*arr[0]);j++) |
| { |
| |
| printf("arr[%d] = %d\n", i, *(*(arr + i) + j)); |
| } |
| } |
| return 0; |
| } |
| |
| 00BCF8F4 |
| 00BCF8F4 |
| 00BCF930 |
| 00BCF930 |
| arr[0] = 1 |
| arr[0] = 2 |
| arr[0] = 3 |
| arr[1] = 4 |
| arr[1] = 5 |
| arr[1] = 6 |
| arr[2] = 7 |
| arr[2] = 8 |
| arr[2] = 9 |
| |
| E:\C\Demo\Project3\指针\Debug\指针.exe (进程 10292)已退出,代码为 0。 |
| 按任意键关闭此窗口. . . |
# 7.3.7 指针数组和二级指针建立关系🌲
| #include <stdio.h> |
| |
| int main(void) |
| { |
| int a[] = { 1, 2, 3 }; |
| int b[] = { 4, 5, 6 }; |
| int c[] = { 7, 8, 9,10 }; |
| int* arr[] = { a, b, c }; |
| |
| int** p = arr; |
| printf("%p\n", arr); |
| printf("%p\n", arr[0]); |
| printf("%d\n", arr[0][0]); |
| printf("%p\n", p); |
| printf("%p\n", *p); |
| printf("%d\n", **p); |
| printf("---------------\n"); |
| |
| |
| printf("%d\n", *(*p) + 1); |
| int i = 0; |
| for (;i < sizeof(arr)/ sizeof(arr[0]); i++) |
| { |
| int j = 0; |
| for (;j < sizeof(arr)/ sizeof(arr[0][0]); j++) |
| { |
| printf("arr[%d] = %d\t", i, *(*(p + i) + j)); |
| } |
| printf("\n"); |
| } |
| return 0; |
| } |
| |
| 006FF9FC |
| 006FFA3C |
| 1 |
| 006FF9FC |
| 006FFA3C |
| 1 |
| --------------- |
| 2 |
| arr[0] = 1 arr[0] = 2 arr[0] = 3 |
| arr[1] = 4 arr[1] = 5 arr[1] = 6 |
| arr[2] = 7 arr[2] = 8 arr[2] = 9 |
| |
| E:\C\Demo\Project3\指针\Debug\指针.exe (进程 8740)已退出,代码为 0。 |
| 按任意键关闭此窗口. . . |
# 7.4 多级指针🌳
- C 语言允许有多级指针存在,在实际的程序中一级指针最常用,其次是二级指针。
- 二级指针就是指向一个一级指针变量地址的指针。
| #include <stdio.h> |
| |
| int main(void) |
| { |
| int a = 10; |
| int b = 20; |
| int* p = &a; |
| int** pp = &p; |
| int*** ppp = &pp; |
| |
| * *p = a |
| * **pp = *p = a |
| * ***ppp = **pp = *p = a |
| */ |
| *pp = &b; |
| **pp = 100; |
| |
| printf("%d\n", *p); |
| **ppp = &a; |
| ***ppp = 200; |
| printf("%d\n", ***ppp); |
| printf("%p\n", *ppp); |
| return 0; |
| } |
| |
| 100 |
| 200 |
| 007EF92C |
| |
| E:\C\Demo\Project3\指针\Debug\指针.exe (进程 1056)已退出,代码为 0。 |
| 按任意键关闭此窗口. . . |
# 7.5 指针和函数🌳
# 7.5.1 函数形参改变实参的值🌲
| #include <stdio.h> |
| |
| extern void swap(int, int); |
| extern void swap1(int, int); |
| |
| int main(void) |
| { |
| int a = 10; |
| int b = 20; |
| |
| swap(a, b); |
| printf("a = %d, b = %d\n", a, b); |
| |
| printf("------------------\n"); |
| |
| int a1 = 10; |
| int b1 = 20; |
| |
| swap1(&a1, &b1); |
| printf("a1 = %d, b1 = %d\n", a1, b1); |
| return 0; |
| } |
| |
| void swap(int a, int b) |
| { |
| int temp = a; |
| a = b; |
| b = temp; |
| } |
| |
| void swap1(int* a, int* b) |
| { |
| int temp = *a; |
| *a = *b; |
| *b = temp; |
| } |
| |
| a = 10, b = 20 |
| ------------------ |
| a1 = 20, b1 = 10 |
| |
| E:\C\Demo\Project3\指针\Debug\指针.exe (进程 16436)已退出,代码为 0。 |
| 按任意键关闭此窗口. . . |
# 7.5.2 字符串拼接指针版🌲
| #include <stdio.h> |
| |
| extern void my_strcat(char*, char*); |
| |
| int main(void) |
| { |
| char ch[100] = "hello"; |
| char ch1[] = "world !!!"; |
| my_strcat(ch, ch1); |
| printf("%s\n", ch); |
| return 0; |
| } |
| |
| void my_strcat(char* ch1, char* ch2) |
| { |
| |
| while (*ch1)ch1++; |
| |
| while (*ch1++ = *ch2++); |
| |
| * 第一步:先取出 ch1 与 ch2 的值 |
| * 第二步:将 ch2 的值 赋值 给 ch1 |
| * 第三步:判断是否满足条件这里判断的是其中一个是否为 0 |
| * 第四步:各自进行 ++ 操作,因为这是后 ++,在第一次赋值时没有进行 ++ 直接 |
| * 赋值,第二次进行 ++,所以说最后一个多走一步将其赋值为 \0 这样防止了乱码 |
| */ |
| } |
| |
| helloworld !!! |
| |
| E:\C\Demo\Project3\指针\Debug\指针.exe (进程 10048)已退出,代码为 0。 |
| 按任意键关闭此窗口. . . |
# 7.5.3 字符串去除空格🌲
| #include <stdio.h> |
| |
| extern char* remove_str_space(char*); |
| extern void remove_str_space1(char*); |
| |
| int main(void) |
| { |
| char ch[] = " h e ll o wr o l d ! ! "; |
| char* p = remove_str_space(ch); |
| printf("p = %s\n", p); |
| |
| remove_str_space1(ch); |
| printf("ch = %s\n", ch); |
| return 0; |
| } |
| |
| char* remove_str_space(char* str) |
| { |
| char str_new[100] = ""; |
| printf("str_new[0] 内存地址: %p\n", str_new[0]); |
| char* p = str_new; |
| while (*str) |
| { |
| if (*str <= 122 && *str >= 97 || *str <= 90 && *str >= 65 || *str == 33) |
| { |
| *p++ = *str; |
| } |
| str++; |
| } |
| printf("*p 内存地址:%p\n", *p); |
| |
| return str_new; |
| } |
| |
| void remove_str_space1(char* str) |
| { |
| char* first = str; |
| char* last = str; |
| while (*last) |
| { |
| if (*last != ' ') |
| { |
| *first = *last; |
| first++; |
| } |
| last++; |
| } |
| *first = '\0'; |
| } |
| |
| str_new[0] 内存地址: 00000000 |
| *p 内存地址:00000000 |
| p = hellowrold!! |
| ch = hellowrold!! |
| |
| E:\C\Demo\Project3\指针\Debug\指针.exe (进程 6388)已退出,代码为 0。 |
| 按任意键关闭此窗口. . . |
# 7.5.4 指针作为函数的返回值🌲
| #include <stdio.h> |
| |
| extern char* search(char*, char); |
| |
| int main(void) |
| { |
| char ch[] = "hello world"; |
| printf("ch[3] = %p\n", ch[3]); |
| char* p = search(ch, 'l'); |
| |
| |
| if (!p == NULL) |
| { |
| |
| if (*p == ch[3]) |
| { |
| printf("p = %p\n", *p); |
| } |
| else |
| { |
| printf("这是一个不符合规则的地址"); |
| } |
| } |
| else |
| { |
| printf("没有找到\n"); |
| } |
| return 0; |
| } |
| |
| char* search(char* str, char ch) |
| { |
| while (*str) |
| { |
| if (*str++ == ch) |
| { |
| return &*str; |
| } |
| } |
| return NULL; |
| } |
| |
| ch[3] = 0000006C |
| p = 0000006C |
| |
| E:\C\Demo\Project3\指针\Debug\指针.exe (进程 18712)已退出,代码为 0。 |
| 按任意键关闭此窗口. . . |
# 7.5.5 字符串查找字符串🌲
| #include <stdio.h> |
| |
| extern char* my_strsearch(char*, char*); |
| |
| int main() |
| { |
| char ch[] = "helle worldllo"; |
| char dest[] = "llo"; |
| char* p = my_strsearch(ch, dest); |
| printf("%s\n", p); |
| return 0; |
| } |
| |
| char* my_strsearch(char* src, char* dest) |
| { |
| char* fsrc = src; |
| char* rsrc = src; |
| char* tdest = dest; |
| while (*fsrc) |
| { |
| |
| rsrc = fsrc; |
| |
| while (*fsrc == *tdest && *fsrc && *tdest) |
| { |
| |
| fsrc++; |
| tdest++; |
| } |
| |
| if (*tdest == '\0') |
| { |
| |
| return rsrc; |
| } |
| |
| tdest = dest; |
| fsrc = rsrc; |
| |
| fsrc++; |
| } |
| return NULL; |
| } |
| |
| llo |
| |
| E:\C\Demo\Project3\指针\Debug\指针.exe (进程 12520)已退出,代码为 0。 |
| 按任意键关闭此窗口. . . |
# 7.6 指针和字符串🌳
# 7.7.1 字符指针🌲
| #include <stdio.h> |
| |
| int main(void) |
| { |
| char ch[] = "hello world"; |
| |
| char* p = "hello world"; |
| |
| char* p1 = "hello world"; |
| |
| printf("%p\n", p); |
| printf("%p\n", p1); |
| ch[2] = 'm'; |
| |
| |
| printf("%s\n", ch); |
| printf("%s\n", p); |
| return 0; |
| } |
| |
| 0008AC64 |
| 0008AC64 |
| hemlo world |
| hello world |
| |
| E:\C\Demo\Project3\指针\Debug\指针.exe (进程 22316)已退出,代码为 0。 |
| 按任意键关闭此窗口. . . |
# 7.7.2 指针和字符串🌲
| int main(void) |
| { |
| |
| |
| char ch[] = "hello"; |
| char ch1[] = "world"; |
| char ch2[] = "des"; |
| |
| char* arr[] = { ch, ch1, ch2 }; |
| |
| char* arr1[] = { "hello", "world", "des" }; |
| |
| int len = sizeof(arr) / sizeof(arr[0]); |
| int flag = 1; |
| int i = 0; |
| for (; i < len; i++) |
| { |
| int j = 0; |
| for (; j < len - 1 - i; j++) |
| { |
| |
| if (arr1[j][0] > arr1[j + 1][0]) |
| { |
| |
| flag = 0; |
| |
| int temp = arr1[j + 1]; |
| arr1[j + 1] = arr1[j]; |
| arr1[j] = temp; |
| } |
| } |
| if (flag) |
| { |
| break; |
| } |
| else |
| { |
| flag = 1; |
| } |
| } |
| int i1 = 0; |
| for (;i1 < sizeof(arr1)/ sizeof(arr1[0]);i1++) |
| { |
| printf("arr1[%d] = %s\n", i1, arr1[i1]); |
| |
| * des |
| * hello |
| * world |
| */ |
| } |
| |
| int i2 = 0; |
| for (; i2 < sizeof(arr1) / sizeof(arr1[0]); i2++) |
| { |
| printf("arr1 -> p = %p\n", arr1[i2]); |
| |
| * 001DBB44 |
| * 001DBC34 |
| * 001DBD00 |
| */ |
| } |
| return 0; |
| } |
| |
| arr1[0] = des |
| arr1[1] = hello |
| arr1[2] = world |
| arr1 -> p = 00A4BD08 |
| arr1 -> p = 00A4BC34 |
| arr1 -> p = 00A4BD00 |
| |
| E:\C\Demo\Project3\指针\Debug\指针.exe (进程 17428)已退出,代码为 0。 |
| 按任意键关闭此窗口. . . |
# 7.7.3 有效字符串🌲
| #include <stdio.h> |
| |
| extern int my_strlen(char*); |
| |
| int main(void) |
| { |
| char ch[] = "hello world"; |
| int len = my_strlen(ch); |
| printf("字符串的有效长度:%d\n", len); |
| return 0; |
| } |
| |
| int my_strlen(char* ch) |
| { |
| int i = 0; |
| char* temp = ch; |
| while (*temp) if (*temp == ' ') { i++; temp++; }else temp++;; |
| return temp - ch - i; |
| } |
| |
| 字符串的有效长度:10 |
| |
| E:\C\Demo\Project3\指针\Debug\指针.exe (进程 21028)已退出,代码为 0。 |
| 按任意键关闭此窗口. . . |
# 7.7.4 const 修饰指针变量🌲
| #include <stdio.h> |
| |
| int main(void) |
| { |
| char ch[] = "hello"; |
| char ch1[] = "world"; |
| |
| |
| const char* p = ch; |
| |
| |
| |
| p = ch1; |
| printf("p = %s\n", p); |
| |
| char* const p1 = ch; |
| *p1 = 'a'; |
| *(p1 + 1) = 'b'; |
| p1[2] = 'c'; |
| |
| printf("p1 = %s\n", p1); |
| return 0; |
| } |
| |
| p = world |
| p1 = abclo |
| |
| E:\C\Demo\Project3\指针\Debug\指针.exe (进程 19260)已退出,代码为 0。 |
| 按任意键关闭此窗口. . . |
# 7.7.4.1 通过多级指针修改 常量指针常量🌴
| #include <stdio.h> |
| |
| int main(void) |
| { |
| char ch[] = "hello"; |
| char ch1[] = "world"; |
| |
| |
| |
| const char* p = ch; |
| |
| |
| |
| p = ch1; |
| printf("p = %s\n", p); |
| |
| |
| |
| |
| char* const p1 = ch; |
| *p1 = 'a'; |
| *(p1 + 1) = 'b'; |
| p1[2] = 'c'; |
| |
| printf("p1 = %s\n", p1); |
| |
| |
| const char* const p2 = ch1; |
| |
| |
| |
| |
| |
| |
| const char** const pp = &p2; |
| |
| |
| |
| |
| |
| |
| char*** ppp = &pp; |
| ***ppp = 'a'; |
| *(*(*ppp) + 1) = 'b'; |
| |
| |
| |
| **ppp[2] = 'c'; |
| printf("%s\n", **ppp); |
| return 0; |
| } |
| |
| p = world |
| p1 = abclo |
| |
| E:\C\Demo\Project3\指针\Debug\指针.exe (进程 16580)已退出,代码为 -1073741819。 |
| 按任意键关闭此窗口. . |
# 7.7.5 指针数组作为 main 函数的形参🌲
| int main(int argc, char* argv[]){}; |
- main 函数是操作系统调用的,第一个参数标明 argc 数组的成员数量,argv 数组的每个成员都是 char * 类型
- argv 是命令行参数的字符串数组
- argc 代表命令行参数的数量,程序名字本身算一个参数
| #include <stdio.h> |
| |
| |
| |
| |
| |
| int main(int argc, char* argv[]) |
| { |
| int i = 0; |
| for (;i < argc;i++) |
| { |
| printf("%s\n", argv[i]); |
| } |
| return 0; |
| } |
打开 cmd 进行编译.c 文件
<font color='red'> 提示 </font>:如果出现了如下的错误提示,则需要使用 -std=c99 来进行编译就不会报错了。
编译完成!
进行如下操作:执行.exe 可执行程序 后面跟上 三句话 ,如果空格分隔会被解析为下一个参数,最终输出三个参数的语句。
可以对其进行一些判断限制
| #include <stdio.h> |
| |
| |
| |
| |
| |
| int main(int argc, char* argv[]) |
| { |
| if (argc < 3) |
| { |
| printf("缺少参数:至少3个以上 !"); |
| return -1; |
| } |
| int i = 0; |
| for (;i < argc;i++) |
| { |
| printf("%s\n", argv[i]); |
| } |
| return 0; |
| } |
执行可执行程序的路径到这个文件 也算是一个参数
# 7.7.6 项目开发常用字符串应用模型🌲
# 1 strstr 中的 while 和 do-while 模型🌴
利用 strstr 标准库函数找出一个字符串中 substr 出现的个数。
# a) while 模型🎋
| #include <stdio.h> |
| |
| extern char* my_strSearchstr(char*, char*); |
| |
| int main(void) |
| { |
| char str[] = "11abcd111122abcd333abcd3322abcd3333322qqq"; |
| char ch1[] = "abcd"; |
| char* p = my_strSearchstr(str, ch1); |
| int count = 0; |
| while (p) |
| { |
| count++; |
| p += strlen(ch1); |
| p = my_strSearchstr(p, ch1); |
| printf("判断第%d次,p = %s\n", count, p); |
| } |
| printf("出现的次数:%d\n", count); |
| return 0; |
| } |
| |
| char* my_strSearchstr(char* src, char* dest) |
| { |
| char* fsrc = src; |
| char* tsrc = src; |
| char* fdest = dest; |
| while (*fsrc) |
| { |
| tsrc = fsrc; |
| while (*fsrc == *fdest && *fsrc && *fdest) |
| { |
| fsrc++; |
| fdest++; |
| } |
| if (*fdest == '\0') |
| { |
| return tsrc; |
| } |
| |
| fsrc = tsrc; |
| fdest = dest; |
| fsrc++; |
| } |
| return NULL; |
| } |
| |
| 判断第1次,p = abcd333abcd3322abcd3333322qqq |
| 判断第2次,p = abcd3322abcd3333322qqq |
| 判断第3次,p = abcd3333322qqq |
| 判断第4次,p = (null) |
| 出现的次数:4 |
| |
| E:\C\Demo\Project3\指针\Debug\指针.exe (进程 19836)已退出,代码为 0。 |
| 按任意键关闭此窗口. . . |
# b) do-while 模型🎋
| #include <stdio.h> |
| |
| extern char* strSearchstrtwo(char*, char*); |
| |
| int main(void) |
| { |
| char str[] = "11abcd111122abcd333abcd3322abcd3333322qqq"; |
| char ch1[] = "abcd"; |
| char* p = strSearchstrtwo(str, ch1); |
| int count = 0; |
| do |
| { |
| |
| if (p) |
| { |
| count++; |
| p += strlen(ch1); |
| p = strSearchstrtwo(p, ch1); |
| printf("判断第%d次:%s\n", count, p); |
| } |
| } while (p); |
| printf("出现的次数:%d\n", count); |
| return 0; |
| } |
| |
| char* strSearchstrtwo(char* src, char* dest) |
| { |
| char* fsrc = src; |
| char* tsrc = src; |
| char* fdest = dest; |
| while (*fsrc) |
| { |
| tsrc = fsrc; |
| while (*fsrc == *fdest && *fsrc && *fdest) |
| { |
| fsrc++; |
| fdest++; |
| } |
| if (*fdest == '\0') |
| { |
| return tsrc; |
| } |
| fsrc = tsrc; |
| fdest = dest; |
| fsrc++; |
| } |
| return NULL; |
| } |
| |
| 判断第1次:abcd333abcd3322abcd3333322qqq |
| 判断第2次:abcd3322abcd3333322qqq |
| 判断第3次:abcd3333322qqq |
| 判断第4次:(null) |
| 出现的次数:4 |
| |
| E:\C\Demo\Project3\指针\Debug\指针.exe (进程 9556)已退出,代码为 0。 |
| 按任意键关闭此窗口. . . |
# 2 两头堵模型🌴
| #include <stdio.h> |
| |
| extern int getCount(char*); |
| |
| int mcoacoicon(void) |
| { |
| char ch[] = " he l l o w orld ! "; |
| int count = getCount(ch); |
| printf("个数为:%d\n", count); |
| return 0; |
| } |
| |
| int getCount(char* src) |
| { |
| int i = 0; |
| char* temp = src; |
| while (*temp)if (*temp == ' ') { i++; temp++; }else temp++; |
| return temp - src - i; |
| } |
| |
| 个数为:11 |
| |
| E:\C\Demo\Project3\指针\Debug\指针.exe (进程 3856)已退出,代码为 0。 |
| 按任意键关闭此窗口. . . |
# 3 计算每个字符出现的次数🌴
| #include <stdio.h> |
| |
| int main(void) |
| { |
| char ch[] = "h hl lo abb worl dni chou s hachouniz ad izaichouyigeshishi"; |
| |
| int ch1[26] = { 0 }; |
| int i = 0; |
| int space = 0; |
| while (ch[i]) |
| { |
| if (ch[i] != ' ') |
| { |
| ch1[ch[i] - 'a']++; |
| } |
| else if(ch[i] == ' ') |
| { |
| space++; |
| } |
| i++; |
| } |
| int j = 0; |
| int space_out = 0; |
| for (; j < 26; j++) |
| { |
| if (ch1[j] != 0) |
| { |
| if (space > 0 && space_out == 0) |
| { |
| printf("空格出现次数:%d\n", space); |
| space_out++; |
| } |
| else |
| { |
| printf("字母: %c 出现次数:%d\n", j + 'a', ch1[j]); |
| } |
| } |
| } |
| return 0; |
| } |
| |
| 空格出现次数:11 |
| 字母: b 出现次数:2 |
| 字母: c 出现次数:3 |
| 字母: d 出现次数:2 |
| 字母: e 出现次数:1 |
| 字母: g 出现次数:1 |
| 字母: h 出现次数:8 |
| 字母: i 出现次数:7 |
| 字母: l 出现次数:3 |
| 字母: n 出现次数:2 |
| 字母: o 出现次数:5 |
| 字母: r 出现次数:1 |
| 字母: s 出现次数:3 |
| 字母: u 出现次数:3 |
| 字母: w 出现次数:1 |
| 字母: y 出现次数:1 |
| 字母: z 出现次数:2 |
| |
| E:\C\Demo\Project3\指针\Debug\指针.exe (进程 16252)已退出,代码为 0。 |
| 按任意键关闭此窗口. . . |
# 4 字符串反转模型 (逆置)🌴
| #include <stdio.h> |
| |
| extern void inver(char*); |
| extern void inver1(char*); |
| |
| int main(void) |
| { |
| char ch[] = "hello world"; |
| inver1(ch); |
| printf("%s\n", ch); |
| return 0; |
| } |
| |
| void inver(char* src) |
| { |
| int i = 0; |
| int j = strlen(src) - 1; |
| while (i < j / 2) |
| { |
| char ch = src[i]; |
| src[i] = src[j]; |
| src[j] = ch; |
| i++; |
| j--; |
| } |
| } |
| |
| void inver1(char* src) |
| { |
| char* fir = src; |
| char* len = src + strlen(src) - 1; |
| printf("%p\n", fir); |
| printf("%p\n", len); |
| |
| while (fir < len) |
| { |
| char ch = *len; |
| *len = *fir; |
| *fir = ch; |
| fir++; |
| len--; |
| } |
| } |
| |
| 0133F92C |
| 0133F936 |
| dlrow olleh |
| |
| E:\C\Demo\Project3\指针\Debug\指针.exe (进程 17920)已退出,代码为 0。 |
| 按任意键关闭此窗口. . . |
# 5 回文字符串🌴
| #include <stdio.h> |
| |
| |
| extern int isSymm(char*); |
| |
| int main(void) |
| { |
| char ch[] = "abcbae"; |
| int flag = isSymm(ch); |
| if (flag) |
| { |
| printf("yes"); |
| } |
| else |
| { |
| printf("no"); |
| } |
| return 0; |
| } |
| |
| int isSymm(char* src) |
| { |
| |
| char* first = src; |
| |
| char* last = src + strlen(src) - 1; |
| |
| while (first < last) |
| { |
| if (*first != *last) |
| { |
| return 0; |
| } |
| first++; |
| last--; |
| } |
| return 1; |
| } |
| |
| no |
| E:\C\Demo\Project3\指针\Debug\指针.exe (进程 9120)已退出,代码为 0。 |
| 按任意键关闭此窗口. . . |
# 7.7.7 字符串处理函数🌲
# 1 strcpy()🌴
| #include <string.h> |
| char* strcpy(char* dest, const char* src); |
| 功能:把src所指向的字符串复制到dest所指向的空间中,'\0' 也会拷贝过去 |
| 参数: |
| dest:目的字符串首地址 |
| src:源字符首地址 |
| 返回值: |
| 成功:返回dest字符串的首地址 |
| 失败:NULL |
<font color='red'> 注意 </font>:如果参数 dest 所指的内存空间不够大,可能会造成缓冲溢出的错误情况。
| #include <stdio.h> |
| |
| extern void my_strcpy(char*, char*); |
| |
| int main() |
| { |
| char ch[] = "hello world"; |
| char str[100]; |
| |
| strcpy(str, ch); |
| my_strcpy(str, ch); |
| printf("strcpy = %s\n", str); |
| printf("my_strcpy = %s\n", str); |
| return 0; |
| } |
| |
| void my_strcpy(char* src, char* dest) |
| { |
| while (*src++ = *dest++); |
| } |
| |
| strcpy = hello world |
| my_strcpy = hello world |
| |
| E:\C\Demo\Project3\指针\Debug\指针.exe (进程 4020)已退出,代码为 0。 |
| 按任意键关闭此窗口. . . |
# 2 strncpy()🌴
| #include <string.h> |
| char* strncpy(char* dest, const char* src, size_t n); |
| 功能:把src指向字符串的前n个字符复制到dest所指向的空间中,是否拷贝结束符看指定的长度是否包含 '\0' |
| 参数: |
| dest:目的字符串首地址 |
| src:源字符首地址 |
| n:指定需要拷贝字符串个数 |
| 返回值: |
| 成功:返回dest字符串的首地址 |
| 失败:NULL |
<font color='red'> 注意 </font>:如果参数 dest 所指的内存空间不够大,可能会造成缓冲溢出的错误情况。
| #include <stdio.h> |
| |
| extern void my_strncpy(char*, char*, int); |
| extern void my_strncpy1(char*, char*, int); |
| |
| int main() |
| { |
| char ch[] = "helloworld"; |
| char str[100] = ""; |
| |
| my_strncpy(str, ch, 5); |
| printf("my_strncpy = %s\n", str); |
| return 0; |
| } |
| |
| void my_strncpy(char* src, char* dest, int v) |
| { |
| while ((*src++ = *dest++) && --v); |
| |
| } |
| |
| void my_strncpy1(char* src, char* dest, int v) |
| { |
| |
| |
| |
| while (v-- && *dest) |
| { |
| *src = *dest; |
| src++; |
| dest++; |
| } |
| } |
| |
| my_strncpy = hello |
| |
| E:\C\Demo\Project3\指针\Debug\指针.exe (进程 17552)已退出,代码为 0。 |
| 按任意键关闭此窗口. . . |
# 3 strcat()🌴
| #include <string.h> |
| char* strcat(char* dest, const char* src); |
| 功能:将src字符串连接到dest的尾部,'\0' 也会追加过去 |
| 参数: |
| dest:目的字符串首地址 |
| src:源字符串首地址 |
| 返回值: |
| 成功:返回dest字符串的首地址 |
| 失败:NULL |
<font color='red'> 注意 </font>:需要保证被追加的字符串初始长度有追加字符串的长度空间大小否则报错
| #include <stdio.h> |
| |
| extern void my_strcatp(char*, char*); |
| |
| int main(void) |
| { |
| |
| char ch[100] = "hello"; |
| char src[] = "world"; |
| |
| my_strcatp(ch, src); |
| printf("%s\n", ch); |
| return 0; |
| } |
| |
| void my_strcatp(char* src, char* ch) |
| { |
| while (*src)src++; |
| while (*src++ = *ch++); |
| } |
| |
| helloworld |
| |
| E:\C\Demo\Project3\指针\Debug\指针.exe (进程 22036)已退出,代码为 0。 |
| 按任意键关闭此窗口. . . |
# 4 strncat()🌴
| #include <string.h> |
| char* strncat(char* dest, const char* src, size_t n); |
| 功能:将src字符串前n个字符连接到dest的尾部,'\0' 也会追加过去 |
| 参数: |
| dest:目的字符串首地址 |
| src:源字符首地址 |
| n:指定需要追加字符串个数 |
| 返回值: |
| 成功:返回dest字符串的首地址 |
| 失败:NULL |
<font color='red'> 注意 </font>:需要保证被追加的字符串初始长度有追加字符串的长度空间大小否则报错,可以进行限制追加字符串的长度
| #include <stdio.h> |
| |
| extern void my_strncatp(char*, char*, int); |
| |
| int main(void) |
| { |
| |
| char ch[100] = "hello"; |
| char src[] = "world"; |
| |
| my_strncatp(ch, src, 3); |
| printf("%s\n", ch); |
| return 0; |
| } |
| |
| void my_strncatp(char* src, char* ch, int v) |
| { |
| while (*src)src++; |
| while ((*src++ = *ch++) && --v); |
| } |
| |
| hellowor |
| |
| E:\C\Demo\Project3\指针\Debug\指针.exe (进程 15568)已退出,代码为 0。 |
| 按任意键关闭此窗口. . . |
# 5 strcmp()🌴
| #include <string.h> |
| int strcmp(const char* s1, const char* s2); |
| 功能:比较s1和s2的大小,比较的是字符ASCII码大小。 |
| 参数: |
| s1:字符串1首地址 |
| s2:字符串2首地址 |
| 返回值: |
| 相等:0 |
| 大于:>0 在不用操作系统strcmp结果会不同,返回ASCII差值 |
| 小于:<0 |
| #include <stdio.h> |
| |
| extern int my_strcmp(char*, char*); |
| |
| int main(void) |
| { |
| char ch[] = "hello world"; |
| char ch1[] = "hello worldl\0 123"; |
| int flag1 = strcmp(ch, ch1); |
| printf("flag1 = %d\n", flag1); |
| int flag = my_strcmp(ch, ch1); |
| printf("flag = %d\n", flag); |
| if (!flag) printf("相同\n"); else printf("不相同\n"); |
| if (flag1 == flag) |
| { |
| printf("函数判断正确\n"); |
| } |
| else |
| { |
| printf("函数判断失误\n"); |
| } |
| return 0; |
| } |
| |
| int my_strcmp(char* src, char* ch) |
| { |
| if (*src == '\0' && *ch != '\0' || *src != '\0' && *ch == '\0') |
| return *src > *ch ? 1 : -1; |
| while (*src && *ch) |
| { |
| if (*src != *ch) |
| { |
| return *src > *ch ? 1 : -1; |
| ch++; |
| src++; |
| } |
| else if (strlen(src) != strlen(ch)) |
| { |
| return strlen(src) > strlen(ch) ? 1 : -1; |
| } |
| ch++; |
| src++; |
| } |
| return 0; |
| } |
| |
| flag1 = -1 |
| flag = -1 |
| 不相同 |
| 函数判断正确 |
| E:\C\Demo\Project3\指针\Debug\指针.exe (进程 8136)已退出,代码为 0。 |
| 按任意键关闭此窗口. . . |
# 6 strncmp()🌴
| #include <string.h> |
| int strncmp(const char** s1, const char* s2, size_t n); |
| 功能:比较s1 和s2 前n个字符的大小,比较的是字符ASCII码大小 |
| 参数: |
| s1:字符串1首地址 |
| s2:字符串2首地址 |
| n:指定比较字符串的数量 |
| 返回值: |
| 相等:0 |
| 大于:>0 在不用操作系统strcmp结果会不同,返回ASCII差值 |
| 小于:<0 |
| #include <stdio.h> |
| |
| extern int my_my_strcmp1(char*, char*, int); |
| |
| int main(void) |
| { |
| char ch[] = "hello world"; |
| char ch1[] = "hallo world\0 123"; |
| int flag1 = strncmp(ch, ch1, 4); |
| printf("flag1 = %d\n", flag1); |
| int flag = my_strcmp1(ch, ch1, 4); |
| printf("flag = %d\n", flag); |
| if (!flag) printf("相同\n"); else printf("不相同\n"); |
| if (flag1 == flag) |
| { |
| printf("函数判断正确\n"); |
| } |
| else |
| { |
| printf("函数判断失误\n"); |
| } |
| return 0; |
| } |
| |
| int my_strcmp1(char* src, char* ch, int v) |
| { |
| |
| if (*src == '\0' && *ch != '\0' || *src != '\0' && *ch == '\0') |
| { |
| |
| return *src > *ch ? 1 : -1; |
| } |
| |
| if (v && strlen(src) >= v && strlen(ch) >= v) |
| { |
| |
| while(*src && *ch && v--) |
| { |
| if (*src != *ch) |
| { |
| return *src > *ch ? 1 : -1; |
| } |
| src++; |
| ch++; |
| } |
| } |
| |
| else |
| { |
| while (*src && *ch) |
| { |
| if (*src != *ch) |
| { |
| return 1; |
| } |
| else if (strlen(src) != strlen(ch)) |
| { |
| return strlen(src) > strlen(ch) ? 1 : -1; |
| } |
| src++; |
| ch++; |
| } |
| } |
| |
| return 0; |
| } |
| |
| flag1 = 1 |
| flag = 1 |
| 不相同 |
| 函数判断正确 |
| |
| E:\C\Demo\Project3\指针\Debug\指针.exe (进程 21484)已退出,代码为 0。 |
| 按任意键关闭此窗口. . . |
# 7 sprintf()🌴
| #include <string.h> |
| int sprintf(char* src, const char* format, ...); |
| 功能:根据参数format字符串来转换并格式化数据,然后将结果输出到str指定的空间中,直到出现字符串结束符 '\0' 为止 |
| 参数: |
| str:字符串首地址 |
| format:字符串格式,用法和printf()一样 |
| 返回值: |
| 成功:实际格式化的字符个数 |
| 失败:-1 |
| #include <stdio.h> |
| |
| int main(void) |
| { |
| char ch[100]; |
| sprintf(ch, "hello world"); |
| printf("%s\n", ch); |
| |
| sprintf(ch, "%s + %s = %d", "a", "b", 3); |
| printf("%s\n", ch); |
| return 0; |
| } |
| |
| hello world |
| a + b = 3 |
| |
| E:\C\Demo\Project3\指针\Debug\指针.exe (进程 17552)已退出,代码为 0。 |
| 按任意键关闭此窗口. . . |
# 8 sscanf()🌴
| #include <string.h> |
| int sscanf(const char* str, const char* format, ...); |
| 功能:从str指定的字符串读取数据,并根据参数format字符串来转换并格式化数据。 |
| 参数: |
| str:指定的字符串首地址 |
| format:字符串格式,用法和scanf()一样 |
| 返回值: |
| 成功:参数数目,成功转换的值的个数 |
| 失败:-1 |
| #define _CRT_SECURE_NO_WARNINGS |
| #include <stdio.h> |
| |
| int main(void) |
| { |
| char ch[] = "1+2=3"; |
| int a, b, c; |
| |
| sscanf(ch, "%d+%d=%d", &a, &b, &c); |
| printf("%d\n", a); |
| printf("%d\n", b); |
| printf("=\n"); |
| printf("%d\n", c); |
| |
| char ch1[] = "helloworld"; |
| char ca[100]; |
| char cb[100]; |
| |
| sscanf(ch1, "%3s%s", &ca, &cb); |
| printf("%s\n", ca); |
| printf("%s\n", cb); |
| return 0; |
| } |
| |
| 1 |
| 2 |
| = |
| 3 |
| hel |
| loworld |
| |
| E:\C\Demo\Project3\指针\Debug\指针.exe (进程 15424)已退出,代码为 0。 |
| 按任意键关闭此窗口. . . |
# 9 strchr()🌴
| #include <string.h> |
| char* strchr(const char* s, int c); |
| 功能:在字符串s中查找字母c出现的位置 |
| 参数: |
| s:字符串首地址 |
| c:匹配字母 (字符) |
| 返回值: |
| 成功:返回第一次出现的c地址 |
| 失败:NULL |
| #include <stdio.h> |
| |
| extern char* my_strchr(char*, char); |
| |
| int main(void) |
| { |
| char ch[] = "hello world"; |
| char c = 'l'; |
| |
| char* index = my_strchr(ch, c); |
| printf("%s\n", index); |
| return 0; |
| } |
| |
| char* my_strchr(char* ch, char c) |
| { |
| while (*ch) |
| { |
| if (*ch == c) return ch; |
| ch++; |
| } |
| return NULL; |
| } |
| |
| llo world |
| |
| E:\C\Demo\Project3\指针\Debug\指针.exe (进程 7820)已退出,代码为 0。 |
| 按任意键关闭此窗口. . . |
# 10 strstr()🌴
| #include <string.h> |
| char* strstr(const char* haystack, const char* needle); |
| 功能:在字符串haystack中查找字符串needle出现的位置 |
| 参数: |
| haystack:源字符串首地址 |
| needle:匹配字符串首地址 |
| 返回值: |
| 成功:返回第一次出现的needle地址 |
| 失败:NULL |
| #include <stdio.h> |
| |
| int main(void) |
| { |
| char ch[] = "hello world"; |
| char ch1[] = "llo"; |
| |
| char* index = strstr(ch, ch1); |
| printf("%s\n", index); |
| return 0; |
| } |
| |
| llo world |
| |
| E:\C\Demo\Project3\指针\Debug\指针.exe (进程 6288)已退出,代码为 0。 |
| 按任意键关闭此窗口. . . |
# 11 strtok()🌴
| #include <string.h> |
| char* strtok(char* str, const char* delim); |
| 功能:来将字符串分割成一个个分段,当strtok()在参数s的字符串中发现参数delim中包含的分隔字符时,则会将该字符改为0字符,当连续出现多个时只替换第一个为0 |
| 参数: |
| str:指向预分割字符串中包含的所有字符 |
| 返回值: |
| 成功:分割后字符串首地址 |
| 失败:NULL |
- 在第一次调用时:strtok () 必须给予参数 s 字符串
- 往后的调用则将参数 s 设置成 NULL,每次调用成功则返回指向被分割出片段的指针
| #include <stdio.h> |
| |
| int main(void) |
| { |
| |
| char ch[] = "www.dkx.net"; |
| char* new_str = strtok(ch, "."); |
| printf("%s\n", new_str); |
| new_str = strtok(NULL, "."); |
| printf("%s\n", new_str); |
| new_str = strtok(NULL, "."); |
| printf("%s\n", new_str); |
| return 0; |
| } |
| |
| www |
| dkx |
| net |
| |
| E:\C\Demo\Project3\指针\Debug\指针.exe (进程 5868)已退出,代码为 0。 |
| 按任意键关闭此窗口. . . |
# 12 atoi()🌴
类似于 javaScript 中的 parseInt
| #include <stdlib.h> |
| int atoi(const char* nptr); |
| 功能:atoi()会扫描nptr字符串,跳过前面的空格字符,直到遇到数字或正负号才开始做转换,而遇到非数组或字符串结束符 '\0' 才结束转换,并将结果返回返回值 |
| 参数: |
| nptr:待转换的字符串 |
| 返回值: |
| 成功转换后整数 |
类似的函数有:
- atof ():把一个小数形式的字符串转化为一个浮点数
- atol ():将一个字符串转化为 long 类型
| #include <stdio.h> |
| |
| int main(void) |
| { |
| char ch[] = "-123-456"; |
| |
| int i = atoi(ch); |
| printf("%d\n", i); |
| return 0; |
| } |
| |
| -123 |
| |
| E:\C\Demo\Project3\指针\Debug\指针.exe (进程 12524)已退出,代码为 0。 |
| 按任意键关闭此窗口. . . |
# 8 内存管理🎄
# 8.1 作用域🌳
C 语言变量的作用域分为:
- 代码块作用域 (代码块是 {} 之间一段代码)
- 函数作用域
- 文件作用域
# 8.1.1 局部变量🌲
局部变量也叫 auto 自动变量 (auto 可写可不写) ,一般情况下代码块 {} 内部定义的变量都是自动变量,它有如下特点:
- 在一个函数内定义,只在函数范围内有效
- 在复合语句中定义,只在复合语句中有效
- <font color='red'> 随着函数调用的结束或复合语句的结束局部变量的生命周期也结束 </font>
- 如果没有赋初始值,内容为随机
| #include <stdio.h> |
| |
| int vmvavin(void) |
| { |
| |
| |
| |
| |
| |
| auto int a = 10; |
| |
| int b = 20; |
| return 0; |
| } |
# 8.1.2 静态 (static) 局部变量🌲
- static 局部变量的作用域也是在定义的函数内有效。
- static 局部变量的声明周期和程序运行周期一样,同时 static 局部变量的值 <font color='red'> 只初始化一次,但可以赋值多次 </font>。
- static 局部变量若未赋值以初始值,则由系统自动赋值:数值型变量自动赋初始值 0,字符型变量赋空字符。
| #include <stdio.h> |
| |
| void fun02() |
| { |
| |
| |
| |
| |
| static int b = 10; |
| b++; |
| printf("b = %d\n", b); |
| int a = 10; |
| a++; |
| printf("a = %d\n", a); |
| } |
| |
| int main(void) |
| { |
| |
| |
| int i = 0; |
| for (;i < 10;i++) |
| { |
| fun02(); |
| } |
| |
| return 0; |
| } |
| |
| b = 11 |
| a = 11 |
| b = 12 |
| a = 11 |
| b = 13 |
| a = 11 |
| b = 14 |
| a = 11 |
| b = 15 |
| a = 11 |
| b = 16 |
| a = 11 |
| b = 17 |
| a = 11 |
| b = 18 |
| a = 11 |
| b = 19 |
| a = 11 |
| b = 20 |
| a = 11 |
| |
| E:\C\Demo\Project3\内存管理\x64\Debug\内存管理.exe (进程 14252)已退出,代码为 0。 |
| 按任意键关闭此窗口. . . |
# 8.1.3 全局变量🌲
- 在函数外定义,可被本文件及其它文件中的函数所共用,若其它文件中的函数调用此变量,须用 extern 声明
- 全局变量的生命周期和程序运行周期一样
- <font color='red'> 不同文件的全局变量不可重名 </font>.
02 全局变量.c
| #include <stdio.h> |
| |
| |
| |
| |
| |
| |
| int a = 10; |
| |
| void fun() |
| { |
| a = 100; |
| printf("fun = %d\n", a); |
| } |
| |
| int main(void) |
| { |
| |
| int a = 123; |
| printf("main - a = %p\n", &a); |
| { |
| |
| |
| int a = 456; |
| printf("main - {one} - a = %p\n", &a); |
| printf("main -{one} = %d\n", a); |
| } |
| { |
| |
| |
| a = 789; |
| printf("main - {two} - a = %p\n", &a); |
| printf("main -{two} = %d\n", a); |
| } |
| |
| printf("main = %d\n", a); |
| fun(); |
| fun01(); |
| return 0; |
| } |
| |
| main - a = 000000090C79F8E4 |
| main - {one} - a = 000000090C79F904 |
| main -{one} = 456 |
| main - {two} - a = 000000090C79F8E4 |
| main -{two} = 789 |
| main = 789 |
| fun = 100 |
| fun01 = 1000 |
| |
| E:\C\Demo\Project3\内存管理\x64\Debug\内存管理.exe (进程 10020)已退出,代码为 0。 |
| 按任意键关闭此窗口. . . |
02test.c
| #include <stdio.h> |
| |
| |
| |
| |
| extern int a; |
| |
| void fun01() |
| { |
| a = 1000; |
| printf("fun01 = %d\n", a); |
| } |
# 8.1.4 静态 (static) 全局变量🌲
- static 局部变量的作用域也是在定义的函数内有效
- static 局部变量的声明周期和程序运行周期一样,同时 static 局部变量的值 <font color='red'> 只初始化一次,但可以赋值多次 </font>。
- static 局部变量若未赋予初始值,则由系统自动赋值:数值型变量自动赋值初始值 0,字符型变量赋空字符
静态全局变量.c
| #include <stdio.h> |
| |
| |
| |
| |
| static int d = 10; |
| |
| void fun05() |
| { |
| d = 20; |
| printf("%d\n", d); |
| } |
| |
| int main(void) |
| { |
| printf("%d\n", d); |
| fun05(); |
| |
| |
| return 0; |
| } |
| |
| 10 |
| 20 |
| |
| E:\C\Demo\Project3\内存管理\x64\Debug\内存管理.exe (进程 6724)已退出,代码为 0。 |
| 按任意键关闭此窗口. . . |
03test.c
| #include <stdio.h> |
| |
| extern int d; |
| |
| void fun06() |
| { |
| printf("%d\n", d); |
| } |
# 8.1.5 extern 全局变量声明🌲
extern int a; 声明一个变量,这个全局变量在别的文件中已经定义了,这里只是声明,而不是定义。
# 8.1.6 全局函数和静态函数🌲
在 C 语言中函数默认都是全局的,使用关键字 static 可以将函数声明为静态,函数定义为 static 就意味着这个函数只能在定义这个函数的文件中使用,在其它文件中不能调用,即使在其它文件中声明这个函数都没用。
对于不同文件中的 static 函数名字可以相同。
全局函数和静态函数.c
| #include <stdio.h> |
| |
| |
| extern void sort(int*, int); |
| |
| extern void fun07(); |
| |
| int main(void) |
| { |
| int arr[] = { 9, 1, 5, 6, 8, 2, 7, 10, 4, 3 }; |
| |
| |
| sort(arr, sizeof(arr)/ sizeof(arr[0])); |
| int i = 0; |
| for (;i < sizeof(arr)/ sizeof(arr[0]); i++) |
| { |
| printf("%d\n", arr[i]); |
| } |
| |
| |
| fun07(); |
| return 0; |
| } |
| |
| |
| |
| |
| static void fun07() |
| { |
| printf("hello world\n"); |
| } |
| |
| 1 |
| 2 |
| 3 |
| 4 |
| 5 |
| 6 |
| 7 |
| 8 |
| 9 |
| 10 |
| hello world |
| |
| E:\C\Demo\Project3\内存管理\x64\Debug\内存管理.exe (进程 3108)已退出,代码为 0。 |
| 按任意键关闭此窗口. . . |
04test.c
| void sort(int* src, int len) |
| { |
| int flag = 1; |
| int i = 0; |
| for (;i < len;i++) |
| { |
| int j = 0; |
| for (;j < len-i-1;j++) |
| { |
| if (*(src + j) > *(src + j + 1)) |
| { |
| flag = 0; |
| int temp = *(src + j + 1); |
| *(src + j + 1) = *(src + j); |
| *(src + j) = temp; |
| } |
| } |
| if (flag) |
| { |
| break; |
| } |
| else |
| { |
| flag = 1; |
| } |
| } |
| } |
5test.c
| #include <stdio.h> |
| |
| |
| |
| static void fun07() |
| { |
| printf("hello world\n"); |
| } |
<font color='red'> 注意 </font>:如果不在,全局函数和静态函数中去声明 sort 函数则跳转不到函数定义,如果声明则可以,如下图演示,建议声明因为有意义
<font color='red'> 注意 </font>:
- 运行在不同的函数中使用相同的变量名,它们代表不同的对象,分配不同的单元,互不干扰。
- 同一源文件中,允许全局变量和局部变量同名,在局部变量的作用域内,全局变量不起作用。
- 所有的函数默认都是全局的,意味着所有的函数都不能重名,但如果是 static 函数,那么作用域是文件级的,所以不同的文件 static 函数名是可以相同的。
# 8.1.7 总结🌲
类型 | 作用域 | 生命周期 |
---|
auto 变量 | 一对 {} 内 | 当前函数 |
static 局部变量 | 一对 {} 内 | 整个程序运行期 |
extern 变量 | 整个程序 | 整个程序运行期 |
static 全局变量 | 当前文件 | 整个程序运行期 |
extern 函数 | 整个程序 | 整个程序运行期 |
static 函数 | 当前文件 | 整个程序运行期 |
register 变量 | 一对 {} 内 | 当前函数 |
全局变量 | 整个程序 | 整个程序运行期 |
# 8.2 内存布局🌳
# 8.2.1 内存分区🌲
C 代码经过 <font color='red'> 预处理 </font>,<font color='red'> 编译 </font>,<font color='red'> 汇编 </font>,<font color='red'> 链接 </font> 4 步后生成一个可执行程序。
在 Windows 下,程序是一个普通的可执行文件,以下例出一个二进制可执行文件的基本情况:
| #include <stdio.h> |
| |
| |
| const int f = 5; |
| |
| int a; |
| |
| int a1 = 10; |
| |
| static b; |
| |
| static b1 = 20; |
| int main(void) |
| { |
| |
| int c; |
| |
| int c1 = 30; |
| |
| static int d; |
| |
| static int d1 = 40; |
| |
| char* p = "hello world"; |
| |
| int arr[] = { 1, 2, 3, 4 }; |
| |
| int* pp = arr; |
| printf("全局常量变量:%p\n", &f); |
| printf("未初始化的全局变量:%p\n", &a); |
| printf("初始化的全局变量:%p\n", &a1); |
| printf("未初始化的静态全局变量:%p\n", &b); |
| printf("初始化的静态全局变量:%p\n", &b1); |
| printf("未初始化的局部变量:%p\n", &c); |
| printf("初始化的局部变量:%p\n", &c1); |
| printf("未初始化的静态局部变量:%p\n", &d); |
| printf("初始化的静态局部变量:%p\n", &d1); |
| printf("字符串常量:%p\n", p); |
| printf("int类型数组:%p\n", arr); |
| printf("指针:%p\n", pp); |
| printf("指针地址:%p\n", &pp); |
| return 0; |
| } |
通过上图可以得知,在没有运行程序前,也就是说 <font color='red'> 程序没有加载到内存前 </font>,可执行程序内部已经分好 3 段信息,分别为 < font color='red'> 代码区 (text)</font>,<font color='red'> 数据区 (data) 和未初始化数据区 (bss) </font>3 个部分 (有些人直接把 data 和 bss 合起来叫做静态区或全局区)
存放 CPU 执行的机器指令。通常代码区是可共享的 (即另外的执行程序可以调用它),使其可共享的目的是对于频繁被执行的程序,只需要在内存中有一份代码即可。<font color='red'> 代码区通常是只读的 </font>,使其只读的原因是防止程序意外的修改了它的指令。另外,代码区还规划了局部变量的相关信息
- 全局初始化数据区 / 静态数据区 (data 段)
该区包含了在程序中明确被初始化的全局变量,已经初始化的静态变量 (包括全局静态变量和局部静态变量) 和常量数据 (如字符串常量)。
存入的是全局未初始化变量和未初始化静态变量。未初始化数据区的数据在程序开始执行之前被内核初始化为 0 或者空 (NULL)
程序在加载到内存前,<font color='red'> 代码区和全局区 (data 和 bss) 的大小就是固定的 </font>,程序运行期间 不能改变。然后,运行可执行程序,系统把程序加载到内存,<font color='red'> 除了根据可执行程序的信息分出代码区 (text),数据区 (data) 和未初始化数据区 (bss) 之外,还额外增加了栈区,堆区 </font>。
<center> 内存模型图 </center>
记载的是可执行文件代码段,所有的可执行代码都加载到代码区,这块内存是不可以在运行期间修改的
加载的是可执行文件 BSS 段,位置可以分开也可以紧靠数据段,存储于数据段的数据 (全局未初始化,静态未初始化数据) 的生存周期为整个程序运行过程
- 全局初始化数据区 / 静态数据区 (data segment)
记载的是可执行文件数据段,存储于数据段 (全局初始化,静态初始化数据,文字常量 (只读)) 的数据生存周期为整个程序运行过程
栈是一种先进后出的内存结构,由编译器自动分配释放,存放函数的参数值,返回值,局部变量等。在程序运行过程中实时加载释放,因此,局部变量的生存周期为申请到释放该段栈空间
| #include <stdio.h> |
| |
| extern void swap(int, int); |
| |
| int main(void) |
| { |
| int a = 10; |
| int b = 20; |
| printf("a = %p\n", &a); |
| printf("b = %p\n", &b); |
| swap(a, b); |
| return 0; |
| } |
| |
| void swap(int a, int b) |
| { |
| int temp = a; |
| a = b; |
| b = temp; |
| printf("a = %p\n", &a); |
| printf("b = %p\n", &b); |
| } |
<center> 栈区存储模型 </center>
堆是一个大容器,它的容量要远远大于栈,但没有栈那样先进后出的顺序。用于动态内存分配。堆在内存中位于 BSS 区和栈区之间。一般由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收。
| #include <stdio.h> |
| |
| int main(void) |
| { |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| int* p = (int*) malloc(sizeof(int)); |
| printf("%p\n", p); |
| |
| *p = 123; |
| printf("%d\n", *p); |
| |
| free(p); |
| |
| printf("%p\n", p); |
| *p = 456; |
| printf("%d\n", *p); |
| |
| p = NULL; |
| return 0; |
| } |
| |
| 00C01A70 |
| 123 |
| 00C01A70 |
| 456 |
| |
| E:\C\Demo\Project3\内存管理\Debug\内存管理.exe (进程 16856)已退出,代码为 0。 |
| 按任意键关闭此窗口. . . |
| #include <stdio.h> |
| |
| int main(void) |
| { |
| |
| |
| |
| int* p = (int*)malloc(sizeof(int) * 8200000 * 100 / 3); |
| printf("%p\n", p); |
| |
| if (!p) |
| { |
| printf("内存爆炸了 !!!"); |
| return -1; |
| } |
| return 0; |
| } |
| |
| 031E1040 |
| |
| E:\C\Demo\Project3\内存管理\Debug\内存管理.exe (进程 5360)已退出,代码为 0。 |
| 按任意键关闭此窗口. . . |
# 8.2.2 开辟的堆空间与释放堆空间的位置关系🌲
| #include <stdio.h> |
| #define MAX 10 |
| |
| extern void sort01(int*, int); |
| |
| int main(void) |
| { |
| srand((size_t) time(NULL)); |
| int* p = (int*) malloc (sizeof(int) * MAX); |
| printf("开辟堆空间的内存地址:%p\n", p); |
| int i = 0; |
| for (;i < MAX;i++) |
| { |
| p[i] = rand() % 100; |
| printf("%d\t", p[i]); |
| } |
| sort01(p, MAX); |
| printf("\n--------排序后--------\n"); |
| int j = 0; |
| for (;j < MAX;j++) |
| { |
| printf("%d\t", *p); |
| p++; |
| } |
| printf("\n堆空间指针后移10位:%p\n", p); |
| |
| |
| p -= 10; |
| printf("堆空间指针前移10位:%p\n", p); |
| |
| * 这里会报错 |
| * 因为,指针开辟的位置和释放的位置发生了改变 |
| * 在排序的时候指针 ++ 偏移量已经不在原来的位置了 |
| * 再回来释放这个堆空间就报错了。 |
| */ |
| free(p); |
| return 0; |
| } |
| |
| void sort01(int* src, int len) |
| { |
| int flag = 1; |
| int i = 0; |
| for (;i < len - 1;i++) |
| { |
| int j = 0; |
| for (;j < len - i - 1;j++) |
| { |
| if (*(src + j) > *(src + j + 1)) |
| { |
| flag = 0; |
| int temp = *(src + j + 1); |
| *(src + j + 1) = *(src + j); |
| *(src + j) = temp; |
| } |
| } |
| if (flag) |
| { |
| break; |
| } |
| else |
| { |
| flag = 1; |
| } |
| } |
| } |
| |
| 开辟堆空间的内存地址:012F6848 |
| 67 88 48 44 83 10 85 81 34 84 |
| --------排序后-------- |
| 10 34 44 48 67 81 83 84 85 88 |
| 堆空间指针后移10位:012F6870 |
| 堆空间指针前移10位:012F6848 |
| |
| E:\C\Demo\Project3\内存管理\Debug\内存管理.exe (进程 3996)已退出,代码为 0。 |
| 按任意键关闭此窗口. . . |
# 8.2.3 内存操作函数🌲
# 1 memset()🌴
| #include <string.h> |
| void* memset(void* s, int c, size_t n); |
| 功能:将s的内存区域的前n个字节以参数c填入 |
| 参数: |
| s:需要操作内存s的首地址 |
| c:填充的字符,c虽然参数为int,但必须是unsigned char,范围为 0 ~ 255 |
| n:指定需要设置的大小 |
| 返回值:s的首地址 |
| #include <stdio.h> |
| |
| int main(void) |
| { |
| |
| int* p = (int*)malloc(sizeof(int)*10); |
| |
| |
| memset(p, 1, sizeof(4)*10); |
| |
| * 为什么输出的是 16843009 呢? |
| * 1 个字节 8 个比特位 0000 0000 |
| * 4 个字节 32 个比特位:0000 0000 0000 0000 0000 0000 0000 0000 |
| * 重置为 1 对应的二进制空间为:0001 0001 0001 0001 0001 0001 0001 0001 |
| * 对应的十六进制为:01 01 01 01 4 个字节空间的值 |
| * 再转换为十进制显示:一个 int 类型空间为:16843009 循环 10 次 |
| * 开辟:4 字节 * 10 = 40 字节 |
| * 循环:1:4 2:8 3:12 4:16 5:20 6:24 7:28 |
| * 8:32 9:34 10:38 每循环 4 个字节十进制值为:16843009 |
| */ |
| int i = 0; |
| for (;i < 10;i++) |
| { |
| printf("%d\t", p[i]); |
| } |
| free(p); |
| return 0; |
| } |
| |
| 16843009 16843009 16843009 16843009 16843009 16843009 16843009 168430016843009 16843009 |
| E:\C\Demo\Project3\内存管理\Debug\内存管理.exe (进程 17428)已退出,代码为 0。 |
| 按任意键关闭此窗口. . . |
# 2 memcpy()🌴
| #include <string.h> |
| void* memcpy(void* dest, const void* src, size_t n); |
| 功能:拷贝src所指的内存内容的前n个字节到dest所指的内存地址上 |
| 参数: |
| dest:目的内存首地址 |
| src:源内存首地址,注意:dest和src所指的内存空间不可重叠,可能会导致程序报错 |
| n:需要拷贝的字节数 |
| 返回值:dest的首地址 |
| #include <stdio.h> |
| |
| int main(void) |
| { |
| int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; |
| int* p = (int*)malloc(sizeof(int) * 10); |
| memcpy(p, arr, sizeof(int) * 10); |
| int i = 0; |
| for (;i < 10;i++) |
| { |
| printf("%d\n", p[i]); |
| } |
| free(p); |
| printf("------------------------\n"); |
| char ch[] = "hello\0 world"; |
| char ch1[100]; |
| |
| strcpy(ch1, ch); |
| int i1 = 0; |
| for (; i1 < 13; i1++) |
| { |
| printf("%c", ch1[i1]); |
| } |
| printf("\n------------------------\n"); |
| |
| memcpy(ch1, ch, 13); |
| int i2 = 0; |
| for (; i2 < 13; i2++) |
| { |
| printf("%c", ch1[i2]); |
| } |
| return 0; |
| } |
| |
| 1 |
| 2 |
| 3 |
| 4 |
| 5 |
| 6 |
| 7 |
| 8 |
| 9 |
| 10 |
| ------------------------ |
| hello烫烫烫 |
| ------------------------ |
| hello world |
| E:\C\Demo\Project3\内存管理\Debug\内存管理.exe (进程 17232)已退出,代码为 0。 |
| 按任意键关闭此窗口. . . |
# 3 memmove()🌴
memmove () 功能用法和 memcpy () 一样,区别在于:dest 和 src 所指的内存空间重叠时,memmove () 仍然能处理,不过执行效率比 memcoy () 低些
| #include <stdio.h> |
| |
| int main() |
| { |
| int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; |
| |
| |
| memmove(&arr[5], &arr[3], 20); |
| int i = 0; |
| for (;i < 10;i++) |
| { |
| printf("%d\t", arr[i]); |
| } |
| return 0; |
| } |
| |
| 1 2 3 4 5 4 5 6 7 8 |
| E:\C\Demo\Project3\内存管理\Debug\内存管理.exe (进程 17712)已退出,代码为 0。 |
| 按任意键关闭此窗口. . . |
# 4 memcmp()🌴
| #include <string.h> |
| int memcmp(const void* s1, const void* s2, size_t n); |
| 功能:比较s1和s2所指向内存区域的前n个字节 |
| 参数: |
| s1:内存首地址1 |
| s2:内存首地址2 |
| n:需比较的前n个字节 |
| 返回值: |
| 相等:=0 |
| 大于:>0 |
| 小于:<0 |
| #include <stdio.h> |
| |
| int main(void) |
| { |
| int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; |
| int arr1[] = { 1, 2, 3, 4, 5 }; |
| |
| int value = memcmp(arr, arr1, 20); |
| printf("%d\n", value); |
| char ch[] = "hello\0 world"; |
| char ch1[] = "hello\0 world"; |
| int value1 = memcmp(ch, ch1, 20); |
| printf("%d\n", value); |
| return 0; |
| } |
| |
| 0 |
| 0 |
| |
| E:\C\Demo\Project3\内存管理\Debug\内存管理.exe (进程 8720)已退出,代码为 0。 |
| 按任意键关闭此窗口. . . |
# 8.2.4 内存常见问题🌲
# 1 数组越界🌴
| #include <stdio.h> |
| |
| int main(void) |
| { |
| |
| |
| char* p = (char*)malloc(sizeof(char) * 11); |
| |
| |
| |
| strcpy(p, "hello world"); |
| printf("%s\n", p); |
| free(p); |
| return 0; |
| } |
| |
| hello world |
<font color='red'> 注意 </font>:报错内存需要重新处理就会卡顿主
# 2 野指针🌴
| #include <stdio.h> |
| |
| int main(void) |
| { |
| |
| int* p = (int*)malloc(0); |
| printf("%p\n", p); |
| *p = 100; |
| printf("%d\n", p); |
| free(p); |
| return 0; |
| } |
与上面同样的报错
# 3 堆空间多次释放🌴
| #include <stdio.h> |
| |
| int main(void) |
| { |
| int* p = (int*)malloc(sizeof(int) * 10); |
| free(p); |
| |
| |
| p = NULL; |
| free(p); |
| return 0; |
| } |
# 4 函数传递🌴
两个同级别的指针就是值传递
| #include <stdio.h> |
| |
| void fun08(int* p) |
| { |
| p = (int*)malloc(sizeof(int) * 10); |
| } |
| |
| int main(void) |
| { |
| int* p = NULL; |
| fun08(p); |
| int i = 0; |
| for (;i < 10;i++) |
| { |
| p[i] = i; |
| } |
| free(p); |
| return 0; |
| } |
引用传递
| #include <stdio.h> |
| |
| void fun08(int* p) |
| { |
| p = (int*)malloc(sizeof(int) * 10); |
| printf("形参:%p\n", p); |
| } |
| |
| void fun09(int** p) |
| { |
| *p = (int*)malloc(sizeof(int) * 10); |
| printf("形参:%p\n", *p); |
| } |
| |
| int main(void) |
| { |
| int* p = NULL; |
| fun08(p); |
| fun09(&p); |
| printf("实参:%p\n", p); |
| free(p); |
| return 0; |
| } |
| |
| 形参:01306380 |
| 形参:01306900 |
| 实参:01306900 |
| |
| E:\C\Demo\Project3\内存管理\Debug\内存管理.exe (进程 8452)已退出,代码为 0。 |
| 按任意键关闭此窗口. . . |
# 8.2.5 二级指针对应的堆空间🌲
| #define _CRT_SECURE_NO_WARNINGS |
| #include <stdio.h> |
| |
| int main(void) |
| { |
| |
| |
| int** p = (int**)malloc(sizeof(int) * 5); |
| int i = 0; |
| for (;i < 5;i++) |
| { |
| |
| p[i] = (int*)malloc(sizeof(int) * 3); |
| } |
| int i1 = 0; |
| for (;i1 < 5;i1++) |
| { |
| int j = 0; |
| for (;j < 3;j++) |
| { |
| scanf("%d", &p[i1][j]); |
| } |
| } |
| int i2 = 0; |
| for (;i2 < 5;i2++) |
| { |
| int j = 0; |
| for (;j < 3;j++) |
| { |
| |
| printf("%d ", *(*(p + i2) + j)); |
| } |
| printf("\n"); |
| } |
| int i3 = 0; |
| for (;i3 < 5;i3++) |
| { |
| |
| free(p[i3]); |
| } |
| |
| free(p); |
| return 0; |
| } |
| |
| 1 2 3 |
| 4 5 6 |
| 7 8 9 |
| 10 11 12 |
| 13 14 15 |
| 1 2 3 |
| 4 5 6 |
| 7 8 9 |
| 10 11 12 |
| 13 14 15 |
| |
| E:\C\Demo\Project3\内存管理\Debug\内存管理.exe (进程 12148)已退出,代码为 0。 |
| 按任意键关闭此窗口. . . |
# 9 复合类型 (自定义类型)🎄
# 9.1 结构体🌳
# 9.1.1 概述🌲
数组:描述一组具有相同类型数据的有序集合,用于处理大量相同类型的数据运算。
有时我们需要将不同类型的数据组合成一个有机的整体,如:一个学生有学号 / 姓名 / 性别 / 年龄 / 地址等属性。显然单独定义以上变量比较繁琐,数据不便于管理。
C 语言中给出了另一种构造数据类型 —— 结构体
# 9.1.2 结构体变量的定义和初始化🌲
定义结构体变量的方式:
- 先声明结构体类型再定义变量名
- 直接定义结构体类型变量 (无类型名)
# 9.1.3 结构体成员的使用🌲
| #define _CRT_SECURE_NO_WARNINGS |
| #include <stdio.h> |
| |
| |
| |
| |
| |
| |
| struct student |
| { |
| |
| char name[21]; |
| int age; |
| int score; |
| char address[51]; |
| |
| }; |
| int main(void) |
| { |
| |
| |
| struct student stu; |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| scanf("%s%d%d%s", stu.name, &stu.age, &stu.score, stu.address); |
| printf("姓名:%s\n", stu.name); |
| printf("年龄:%d\n", stu.age); |
| printf("分数:%d\n", stu.score); |
| printf("地址:%s\n", stu.address); |
| return 0; |
| } |
| |
| 张三 18 100 北京朝阳区 |
| 姓名:张三 |
| 年龄:18 |
| 分数:100 |
| 地址:北京朝阳区 |
| |
| E:\C\Demo\Project3\day09\x64\Debug\day09.exe (进程 18160)已退出,代码为 0。 |
| 按任意键关闭此窗口. . . |
# 9.1.4 结构体数组🌲
| #include <stdio.h> |
| |
| struct student |
| { |
| char name[21]; |
| int age; |
| char sex; |
| int score; |
| char addr[51]; |
| }; |
| |
| int main(void) |
| { |
| struct student stu[3] = |
| { |
| { "张三", 18, 'M', 90, "北京" }, |
| { "李四", 20, 'M', 99, "上海" }, |
| { "刘桑", 19, 'M', 100, "邯郸" } |
| }; |
| |
| printf("结构体数组大小:%d\n", sizeof(stu)); |
| |
| |
| printf("结构体元素大小:%d\n", sizeof(stu[0])); |
| printf("结构体元素个数:%d\n", sizeof(stu)/ sizeof(stu[0])); |
| printf("=======学生成绩表=======\n"); |
| int i = 0; |
| for (;i < sizeof(stu) / sizeof(stu[0]);i++) |
| { |
| printf("-----------------------\n"); |
| printf("姓名:%s\n", stu[i].name); |
| printf("年龄:%d\n", stu[i].age); |
| printf("性别:%s\n", stu[i].sex == 'M' ? "男" : "女"); |
| printf("分数:%d\n", stu[i].score); |
| printf("地址:%s\n", stu[i].addr); |
| printf("-----------------------\n"); |
| } |
| return 0; |
| } |
| |
| 结构体数组大小:264 |
| 结构体元素大小:88 |
| 结构体元素个数:3 |
| =======学生成绩表======= |
| ----------------------- |
| 姓名:张三 |
| 年龄:18 |
| 性别:男 |
| 分数:90 |
| 地址:北京 |
| ----------------------- |
| ----------------------- |
| 姓名:李四 |
| 年龄:20 |
| 性别:男 |
| 分数:99 |
| 地址:上海 |
| ----------------------- |
| ----------------------- |
| 姓名:刘桑 |
| 年龄:19 |
| 性别:男 |
| 分数:100 |
| 地址:邯郸 |
| ----------------------- |
| |
| E:\C\Demo\Project3\day09\x64\Debug\day09.exe (进程 11680)已退出,代码为 0。 |
| 按任意键关闭此窗口. . . |