头文件
#define _CRT_SECURE_NO_WARNINGS 1 #include <stdlib.h> #include <time.h> #include <string.h> #include <stdio.h> #include <limits.h> #include <ctype.h> #include <math.h>
内存单元有编号 编号 = 地址 = 指针
指针就是个变量,用来存放地址,地址唯一标识一块内存空间
地址/指针/指针变量的大小是4 / 8个字节(32位/64位平台)
指针类型决定了指针加一减一的步长 指针解引用操作的权限
指针的运算
1.字符指针
把一个字符的地址赋值给指针
字符串表达式的值是首字符的地址
int main() { char ch = 'w'; char* pc = &ch; char arr[] = "abcdef"; const char* p = "abcdef"; printf("%p\n", p); char* p1 = arr;//数组的地址 char* p2 = &arr[0];//数组首元素的地址 printf("%p\n", p1);//000000906378FAD4 printf("%p\n", p2);//000000906378FAD4 printf("%c\n", "abcdef"[3]);//d 想象成地址,访问数组中下标为3的元素 //首字符a的地址赋给了p return 0; }
int main() { char str1[] = "hello bit."; char str2[] = "hello bit."; const char* str3 = "hello bit.";//const+变量:该变量里的数据只能被访问,不能被修改,意味只读 const char* str4 = "hello bit."; if (str1 == str2)//not same 两个数组地址不相同 printf("same\n"); else printf("not same\n"); if (str3 == str4)//same 两个常量字符串比较 常量字符串不能被修改 内容相同时,只会保存一份 printf("same\n"); else printf("not same\n"); if (&str3 == &str4)//no printf("same\n"); else printf("not same\n"); return 0; }
字符串≈数组
常量字符串只能保存一份 地址不同 但是只保存一份常量字符串 所以两字符串很像
2.指针数组——是数组、
存放在数组中的元素是指针类型
整形数组是存放整形的数组
字符数组是存放字符的数组
指针数组是存放指针的数组
存放在数组中的元素是指针类型的
int* arr[5]存放整形指针的数组
char* ch[6]存放字符指针的数组
int main() { int a = 1; int b = 2; int c = 3; int d = 4; //不会这样使用 int* arr[] = { &a,&b,&c,&d }; return 0; }
用指针数组模拟二维数组的
int main() { int arr1[] = { 1,2,3,4,5 }; int arr2[] = { 2,3,4,5,6 }; int arr3[] = { 3,4,5,6,7 }; //int* int* int* int *arr[] = { arr1,arr2,arr3 }; for (int i = 0; i < 3; i++) { for (int j = 0; j < 5; j++) { printf("%d ", arr[i][j]);//通过访问数组下标来访问每一个元素 } printf("\n"); } return 0; }
int main() { //指针数组的应用场景之一 const char* arr[5] = { "hello bit ","hehe ","zezeze ","ijij ","666 " }; for (int i = 0; i < 5; i++) { printf("%s\n", arr[i]); } return 0; }
指针数组可以维护多个数组
指针数组是一种数组形式的存在
3.数组指针是指针,指向数组的指针
指针数组是数组,是存放指针的数组
int main() { int a = 10; int* p1 = &a; char ch = 'c'; char* p2 = &ch; float f = 3.14; float* p3 = &f; return 0; }
数组名的理解 : 数组名是数组首元素的地址
2个例外
sizeof(数组名)表示整个数组,计算整个数组大小,单位字节
& 数组名,这里的数组表示整个数组,表示整个数组的大小
p = &arr
arr & arr & arr[0] 取地址相同 它们的值是一样的
int main() { int arr[10]; printf("arr: %p\n", arr); printf("arr+1:%p\n", arr+1); printf("\n"); printf("&arr: %p\n", &arr);//它们的值相同 printf("&arr+1:%p\n", &arr+1);//它们的值相同 printf("\n"); printf("&arr[0]: %p\n", &arr[0]); printf("&arr[0]+1:%p\n", &arr[0]+1); //指针类型决定了指针+1,到底+几个字节 return 0; }
前两个加一是类型不同 跳过字节
第三个跳过整个数组元素的字节
数组指针的创建方式 : int(*p)[10] = &arr p是用来存放数组的地址的 是数组指针
int main() { int arr[10] = { 0 }; //数组指针的写法: int (*p)[10] = &arr;//p是用来存放数组的地址的,p就是数组指针 //eg、1 char* arr1[5]; char* (*pc)[5] = &arr1; //eg、2 int* arr2[7]; int* (*p)[7] = &arr2; return 0; }
数组指针的用处 :
int main() { //法1: int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; int(*p)[10] = &arr; for (int i = 0; i < 10; i++) { printf("%d ", (*p)[i]); } return 0; }
int main() { //法2: 一维数组正确访问方式: int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; int* p = arr; int i = 0; for (i = 0; i < 10; i++) { printf("%d ", p[i]); } return 0; }
遍历二维数组中的每一个元素
void print(int arr[3][5], int r, int c) // (int (*P)[5],int r,int c) 二维数组传参 形参的部分写的是指针 { for (int i = 0; i < 3; i++) { for (int j = 0; j < 5; j++) { printf("%d ", arr[i][j]); } printf("\n"); } } int main() { int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} }; print(arr, 3, 5);//数组名,行数,列数 二维数组传参 return 0;//arr是二维数组的数组名,是首元素的地址, //二维数组首元素的地址是第一行的地址,第一行的地址是第一行首元素的地址 }
数组传参本质是传指针
void print(int *arr, int sz)//形式参数指针的形式 本质是指针 //void print(int arr[], int sz) { int i = 0; for (i = 0; i < sz; i++) { printf("%d ", arr[i]);//arr[i]===>*(arr+i) } } int main() { int arr[] = { 1,2,3,4,5,6,7,8,9,10 }; int sz = sizeof(arr) / sizeof(arr[0]); print(arr, sz); //一维数组传参 return 0; }
int arr[5];//arr是一个能够存放5个整形数据的数组 int* parr1[10];//parr1是一个数组,数组10个元素,每个元素的类型是int* int(*parr2)[10];//parr2是一个数组指针,该指针指向数组,指向的数组有10个元素,每个元素的类型是int int(*parr3[10])[5];//parr3是一个数组,是存放数组指针的数组,存放的这个数组指针,指向的数组有5个元素,每个元素是int类型
替换二维数组传参 形参使用二维数组的形式
二维数组行可以省略 列不能省略 一维数组可以省略
一维数组传参用指针传参更好理解
二维数组数组名是第一行的地址 第一行的地址是第一行一维数组的地址
二维数组传参 形参的部分传给指针
4.数组参数和指针参数
把数组传参给指针
一维数组 : 数组传参 形参的部分可以写成数组形式
数组传参时 数组的大小随意
数组传参的本质是传递数组首元素的地址
数组传参 形参可以传为指针形式
void test(int arr[]) {}y void tst(int arr[10]) {}y void test(int* arr) {}y void test2(int *arr[20]) {}y void test2(int **arr)//传过去一级指针的地址,所以可以写成二级指针 {}y 数组名表示首元素的地址 每个元素都是int * 类型地址 数组名表示首元素的地址是int * 地址 二维数组传参 void test(int arr[3][5]) {}y void test(int arr[][]) {}N 行可以省略,列不可以省略 void test(int arr[][5]) {}y void test(int *arr) {}N void test(int* arr[5]) {}N 要么写成指针,要么写成数组 void test(int (*arr)[5]) {}y void test(int **arr) {}N 二维数组传参 : 行可以省略 列不能省略 数组名传参传地址传的是第一行的地址
一级指针传参
void print(int* p, int sz) { int i = 0; for (i = 0; i < sz; i++) { printf("%d ", *(p + i)); } } int main() { int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; int* p = arr; int sz = sizeof(arr) / sizeof(arr[0]); //一级指针p,传给函数 print(p, sz); return 0; }
二级指针用来接收一级指针的地址
一级指针传参
传地址和传指针一样 形参部分写成一级指针便可
一级指针用来接收地址
看类型的地址 同一类型的指针都可以传参
当一个函数的参数部分为一级指针的时候,函数能接收什么参数
void test(int* p) { } int main() { int a = 10; int* ptr = &a; int arr[5]; test(arr);//传整形一维数组的数组名 test(&a);//传整型变量的地址 test(ptr);//传整形指针 }
二级指针传参
先给一个一级指针变量
再把一级指针的地址传给二级指针
二级指针传参只能用二级指针的地址传参
#include <stdio.h> void test(int** ptr) { printf("num = %d \n", **ptr); } int main() { int n = 10; int* p = &n; int** pp = &p; //二级指针变量等于一级指针变量的地址 //当一个二级指针变量int **p接收,可以传一个一级指针变量地址,可以传一个二级指针变量 //还可以传一个数组首元素的地址 如:int* arr[6] test(pp); test(&p); return 0; }
数组指针 : 指向数组的指针 存放的是数组的地址 取地址数组名就是数组的地址 &数组名就是数组的地址
函数指针 : 指向函数的指针 存放的是函数的地址 取地址函数名就是函数的地址 &函数名是函数的地址吗
int Add(int x, int y) { return x + y; } int main() { //&函数名和函数名代表的都是函数的地址 int a, b; printf("请您输入两个值\n"); scanf("%d%d", &a, &b); int c = Add(a, b); printf("%p\n", &Add); printf("%p\n", Add); printf("%d", c); int (*pf1)(int, int) = Add; int (*pf2)(int, int) = &Add; //int* pf2(int,int);函数的声明 //由此可见类型相同,两种写法皆可 int ret = (*pf2)(2, 3); int ret1 = Add(2, 3); printf("%d\n", ret); printf("%d\n", ret1); return 0; }
函数名也是函数的地址
与数组指针写法非常类似
函数指针也是一种指针 是函数的地址 需要一个函数的变量
//代码1 int main() { //void(*)()函数指针类型 //int a=(int)3.14; //下面的代码是在调用0地址处的函数,这个函数没有参数,返回类型是void ( *(void (*)( ))0)(); //返回类型void 强转 调用函数无参数 }
//代码2: int main() { //对一个类型重新起名 //typedef unsigned int uint //类型重命名 复杂类型 重命名 void(* signal(int, void(*)(int)))(int); //void(*)(int) signal(int, void(*)(int));//不支持 //signal函数 两参数:int类型 函数指针类型 参数是int,返回类型是void //这个代码是一次函数声明,声明的是signal函数, //signal函数参数有两个,第一个是int类型,第二个是函数指针类型,该类型是void(*), //signal函数的返回类型也是函数指针类型,该类型是void(*)(int) //该函数指针指向的函数,参数是int,返回类型是void return 0; }
代码2太复杂,如何优化:
typedef void(*pfun_t)(int);
pfun_t signal(int, pfun_t);
typedef unsigned int uint
类型重命名 复杂类型 重命名
typedef unsigned int uint;//类型重命名 int main() { uint a; unsigned int b; return 0; }