一、C语言 指针基础
1、内存地址
1、内存含义
- 存储器:计算机的组成中,用来存储程序和数据,辅助CPU进行运算处理的重要部分。
- 内存:内部存贮器,暂存程序/数据——掉电丢失 SRAM、DRAM、DDR、DDR2、DDR3。
- 外存:外部存储器,长时间保存程序/数据—掉电不丢ROM、ERRROM、FLASH(NAND、NOR)、硬盘、光盘。
2、内存是沟通CPU与硬盘的桥梁
- 暂存放CPU中的运算数据
- 暂存与硬盘等外部存储器交换的数据
3、物理存储器和存储地址空间
- 有关内存的两个概念:物理存储器和存储地址空间
4、物理存储器:实际存在的具体存储器芯片
- 主板上装插的内存条
- 显示卡上的显示RAM芯片
- 各种适配卡上的RAM芯片和ROM芯片
5、存储地址空间:对存储器编码的范围。我们在软件上常说的内存是指这一层含义
- 编码:对每个物理存储单元(一个字节)分配一个号码
- 寻址:可以根据分配的号码找到相应的存储单元,完成数据的读写
6、内存地址
- 将内存抽象成一个很大的一维字符数组。
- 编码就是对内存的每一个字节分配一个32位或64位的编号(与32位或者64位处理器相关)。
- 这个内存编号我们称之为内存地址。
7、内存中的每一个数据都会分配相应的地址
- char:占一个字节分配一个地址
- int: 占四个字节分配四个地址
- float、struct、函数、数组等
2、指针变量
一、概述
- 内存区的每一个字节都有一个编号,这就是“地址”。
- 如果在程序中定义了一个变量,在对程序进行编译或运行时,系统就会给这个变量分配内存单元,并确定它的内存地址(编号)
- 指针的实质就是内存“地址”。指针就是地址,地址就是指针。
- 指针是内存单元的编号,指针变量是存放地址的变量。
- 通常我们叙述时会把指针变量简称为指针,实际他们含义并不一样。
二、数据存储格式
注:windos电脑在做数据存储时采用小端对齐。
注:Linux 电脑在做数据存储时采用大端对齐。
三、定义说明
- 指针也是一种数据类型,指针变量也是一种变量
- 指针变量指向谁,就把谁的地址赋值给指针变量
- “*”操作符操作的是指针变量指向的内存空间
注意:&可以取得一个变量在内存中的地址。但是,不能取寄存器变量,因为寄存器变量不在内存里,而在CPU里面,所以是没有地址的。
注意:&是取地址符号是升维度的、*是取值符号是将维度的。
注意:在定义指针类型一定要和变量的类型对应上。
四、案例
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <string.h> #include <stdlib.h> #include <math.h> #include <time.h> int main(void) { // 定义指针变量存储变量地址 int a = 10; // 指针类型:数据类型* int* p; p = &a; // 通过指针间接改变变量的值 *p = 100; printf("%p ", &a); printf("%p ", p); printf("%d ", a); printf("%d ", *p); return 0; }
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <string.h> #include <stdlib.h> #include <math.h> #include <time.h> int main(void) { char ch = 'a'; char* p = &ch; // 所有指针类型存储都内存地址,内存地址都是一个无符号十六进制整形数 // 32位操作系统所有指针类型:内存占用4字节 // 64位操作系统所有指针类型:内存占用8字节 printf("%d ", sizeof(int*)); printf("%d ", sizeof(char*)); return 0; }
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <string.h> #include <stdlib.h> #include <math.h> #include <time.h> int main(void) { int arr[] = { 1,2,3,4,5,6,7,8,9,10 }; // P 和 arr 不同点 // 指向数组指针 int* p = arr; // 相同点 //p[i] //*(p+i) // 不同点 // p 是变量、arr是常量 // p 是一个指针4个字节大小 // arr 是一个数组是40个字节大小 printf("指针类型大小%d ", sizeof(p)); printf("数组大小%d ", sizeof(arr)); // 数组作为函数参数会化为指针、丢失数组的精度 // 通过 int* 将指针变为值 // void BubbleSort(int arr[]) // void BubbleSort(int* arr[],int len) return 0; }
3、野指针
指针变量也是变量,是变量就可以任意赋值,不要越界即可(32位为4字节,64位为8字节),但是,任意数值赋值给指针变量没有意义,因为这样的指针就成了野指针,此指针指向的区域是未知(操作系统不允许操作此指针指向的内存区域)。所以,野指针不会直接引发错误,操作野指针指向的内存区域才会出问题。野指针和有效指针变量保存的都是数值,为了标志此指针变量没有指向任何变量(空闲可用)
int a = 100; int *p; p = a; //把a的值赋值给指针变量p,p为野指针, ok,不会有问题,但没有意义 p = 0x12345678; //给指针变量p赋值,p为野指针, ok,不会有问题,但没有意义 *p = 1000; //操作野指针指向未知区域,内存出问题,err
案例
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <string.h> #include <stdlib.h> #include <math.h> #include <time.h> int main(void) { // 野指针 -> 指针变量指向一个未知的空间 // 不建议将一个变量的值直接赋值给指针 // 程序中允许存在野指针 int* p = 100; // 操作系统将0-255作为系统占用不允许访问操作 // 操作野指针对应的内存空间可能报错 printf("%d ", *p); return 0; }
4、空指针
C语言中,可以把NULL赋值给此指针,这样就标志此指针为空指针,没有任何指针。
// 空指针 int *p = NULL; // NULL是一个值为0的宏常量: #define NULL ((void *)0)
案例
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <string.h> #include <stdlib.h> #include <math.h> #include <time.h> int main(void) { // 空指针是指内存地址编号为0的空间 int* p = NULL; // 操作空指针对应的空间一定会报错 *p = 100; printf("%d ", *p); // 空指针可以用作条件判断 if (p == NULL) { } return 0; }
5、万能指针
void *指针可以指向任意变量的内存空间。
案例
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <string.h> #include <stdlib.h> #include <math.h> #include <time.h> int main(void) { int a = 10; // 万能指针可以接受任意类型变量的内存地址 void * p = &a; // 再通过万能指针修改变量的值时,需要找到变量对应的指针类型 *(int*)p = 100; printf("%d ", a); printf("%d ", *(int*)p); // printf("万能指针在内存占得字节大小:%d ", sizeof(void*)); return 0; }
6、const 修饰指针
const 修饰可以约束指针类型或值得修改,但使用+1级别指针也可以让const无效。
案例
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <string.h> #include <stdlib.h> #include <math.h> #include <time.h> int main(void) { // 1、 // 常量:不允许修改 // 存储空间:栈区(可指针间接修改) const int a = 10; // 指针间接修改常量值 int* p = &a; *p = 100; printf("%d ", a); // 2、 // const 修饰指针类型(内存空间) int a = 10; int b = 20; const int* p = &a; // 可以修改指针变量的值 // 不可以修改指针内存空间的值:*p = 100; p = &b; printf("%d ", *p); // 3、 // const 修饰指针变量 int c = 10; int d = 20; int* const p = &c; // 可以修改指针类型(内存空间)的值 // 不可以修改指针内存空间的值:p = &d; *p = 100; printf("%d ", *p); // 4、 // const 修饰指针变量、类型(内存空间) // 只读指针 int e = 10; int f = 20; // 不可以修改指针内存空间的值:*p = 100; // 不可以修改指针内存空间的值:p = &f; const int* const p = &e; // 二级指针操作 // 可通过二级指针修改一级指针内存空间的值: int** pp = &p; // *pp是一级指针的值(内存空间) *pp = &b; // **pp是变量的值、**代表将了一个维度 **pp = 100; printf("%d ", *p); return 0; }
#include <stdio.h> #include <stdlib.h> #include <string.h> int main(void) { //const修饰一个变量为只读 const int a = 10; //a = 100; //err //指针变量, 指针指向的内存, 2个不同概念 char buf[] = "aklgjdlsgjlkds"; //从左往右看,跳过类型,看修饰哪个字符 //如果是*, 说明指针指向的内存不能改变 //如果是指针变量,说明指针的指向不能改变,指针的值不能修改 const char *p = buf; // 等价于上面 char const *p1 = buf; //p[1] = '2'; //err p = "agdlsjaglkdsajgl"; //ok char * const p2 = buf; p2[1] = '3'; //p2 = "salkjgldsjaglk"; //err //p3为只读,指向不能变,指向的内存也不能变 const char * const p3 = buf; return 0; }
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <string.h> #include <stdlib.h> #include <math.h> #include <time.h> int main(void) { // 定义int类型 const int a = 10; // 直接不可以修改 // a = 100; //err // 通过一级指针修改 int* p = &a; *p = 100; // 定义char类型 char ch1[] = "hello"; char ch2[] = "hello"; // 指向常量指针:可以修改指针变量的值、不可以修改指针变量指向内存空间的值 const char* p = ch1; // *p = 'm';//err // p = ch2;//ok // p[2] = 'm';//err // 定义char类型 char ch3[] = "hello"; char ch4[] = "hello"; // 常量指针:可以修改指针变量指向内存空间的值、不可以修改指针变量的值 char* const p = ch3; // p = ch4;//err // p[2] = 'm';//ok // *(p + 2) = 'm';//ok // 定义char类型 char ch5[] = "hello"; char ch6[] = "hello"; // 不可修改变量与内存 const char* const p = ch5; // p = ch6;//err // p[2] = 'm';//err // *p = 'm';//err // 二级指针 char** p1 = &p; // *p1 = ch2;//ok // *(*p1+1) = 'm';//ok return 0; }
二、C语言 指针使用
1、指针类型运算
- 指针计算不是简单的整数相加
- 如果是一个int *,+1的结果是增加一个int的大小
- 如果是一个char *,+1的结果是增加一个char大小
案例
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <string.h> #include <stdlib.h> #include <math.h> #include <time.h> int main(void) { int arr[] = { 1,2,3,4,5,6,7,8,9,10 }; // 数组名是一个常量 不允许赋值 // 数组名是数组首元素地址 // aar = 100; //err // 创建指针变量 int* p; p = arr; printf("%p ", p); // 打印数组第一个值 printf("%d ", *p); printf("%p ", arr); // *取值(arr内存地址+1) 、相当于arr[1] printf("%d ", *(arr + 1)); // 指针类型变量+1:等同于内存地址+sizeof(类型) printf("%d ", *(p + 1)); // 指针p++:等同于内存地址+sizeof(类型) *p = 123; p++; printf("%p ", arr); printf("%p ", p); for (int i = 0; i < 10; i++) { // 打印数组值 printf("%d ", p[i]); printf("%d ", *(p + i)); // 打印数组值 printf("%d ", *p++); } // 两个指针相减 得到的结果是两个指针的偏移量(步长) // 所有的指针类型 相减结果都是int类型 // 3c 40 +1 相当于 sizeof(int) 40/sizeof(int) int step = p - arr; printf("%d ", step); return 0; }
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <string.h> #include <stdlib.h> #include <math.h> #include <time.h> // 方式一:数组方式实现 void my_strcpy01(char* dest, char* ch) { int i = 0; // 非0为真值 while (ch[i]) { dest[i] = ch[i]; i++; } dest[i] = 0; } // 方式二:以指针偏移量 void my_strcpy02(char* dest, char* ch) { int i = 0; while (*(ch+i)) { *(dest + i) = *(ch + i); i++; } *(dest + i) = 0; } // 方式三:以指针运算方式实现 void my_strcpy03(char* dest, char* ch) { while (*ch) { *dest = *ch; // 指针+1相当于指向数组下一个元素 内存地址变化了sizeof(char) dest++; ch++; } *dest = 0; } // 方式四:以指针加运算方式实现 void my_strcpy04(char* dest, char* ch) { // 第一步:*ch 取值 *dest 取值 // 第二部:*dest = *ch 赋值 // 第三部:判断 值是否非0 // 第四部:ch++ dest++ while (*dest++ = *ch++); } int main(void) { // 指针运算、字符串拷贝 char ch[] = "hello world"; char dest[100]; my_strcpy04(dest, ch); printf("%s ", dest); return 0; }
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <string.h> #include <stdlib.h> #include <math.h> #include <time.h> int main(void) { int arr[] = { 1,2,3,4,5,6,7,8,9,10 }; int* p = arr; // 指针的加减运算和指针的类型有关 p = &arr[3]; p--; p--; p--; // 内存地址相差:12 / sizeof(int) = 偏移量 int step = p - arr; // 指针操作数组时下标允许时负数 // p[-2] = *(p-2); printf("%d ", p[-2]); printf("%p ", p); return 0; }
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <string.h> #include <stdlib.h> #include <math.h> #include <time.h> int main(void) { // 指针和运算符的操作 int arr[] = { 1,2,3,4,5,6,7,8,9,10 }; int* p = arr; // 野指针 // p = p + arr; //err // p = p*arr; //err // p = p*4; //err // p = p/4; //err // p = p%4; //err // 指针判断可以使用、> = < ? && || ... // p = &arr[3] // if (p > arr) { printf("真 ");} // 野指针可以相减 // p = 100; // int step = arr - p; ///printf("%d ",step) return 0; }
2、指针数组
指针数组,它是数组,数组的每个元素都是指针类型。
案例
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <string.h> #include <stdlib.h> #include <math.h> #include <time.h> int main(void) { // 定义数组 数据类型 数据名[元素个数] = {值1,值2} // 定义指针数组 int a = 10; int b = 20; int c = 30; int* arr[3] = { &a,&b,&c }; // arr[0]:为指针数组地址 // *arr[0]:为指针对应值 printf("%d ", arr[0]); printf("%d ", *arr[0]); for (int i = 0; i < sizeof(arr)/sizeof(arr[0]); i++) { // 打印数组内每个值 printf("%d ", *arr[i]); } // 指针数组大小 = 对应类型 * 元素个数 printf("指针数组大小:%d ", sizeof(arr)); printf("指针数组大小:%d ", sizeof(arr[0])); return 0; }
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <string.h> #include <stdlib.h> #include <math.h> #include <time.h> int main(void) { // 指针数组里面元素存储的是指针 int a[] = { 1,2,3 }; int b[] = { 4,5,6 }; int c[] = { 7,8,9 }; // 指针数组时一个特殊的二维数组模型 // 指针数组对应于二级指针 int* arr[] = { a,b,c }; // 打印内存地址 // arr 是指针数组的首地址 printf("%p ", arr[0]); printf("%p ", a); printf("%p ", &a[0]); for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { // 通过二维数组方式打印所有值 printf("%d", arr[i][j]); // 通过偏移量方式打印 printf("%d", *(arr[i]+j)); // 通过偏移量与指针运算方式打印 printf("%d", *(*(arr + i) + j)); } puts(""); } return 0; }
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <string.h> #include <stdlib.h> #include <math.h> #include <time.h> // 数组名做函数参数,函数的形参会退化为指针 // 通过数组写法实现 void my_strcat01(char* ch1, char* ch2) { int i = 0; while (ch1[i] != '