C语言指针
2020-12-13 16:15
标签:argv 扩展 浮点型 主函数 多字节 short main函数 申请 需要 关于内存 存储器 :存储数据器件 外存 外存又叫外部存储器,长期存放数据,掉电不丢失数据 常见的外存设备:硬盘、flash、rom、u盘、光盘、磁带 内存 内存又叫内部存储器,暂时存放数据,掉电数据丢失 常见的内存设备:ram(单片机)、DDR 物理内存:实实在在存在的存储设备 虚拟内存:操作系统虚拟出来的内存 32 bit 32根 0x00 00 00 00 0xff ff ff ff 操作系统会在物理内存和虚拟内存之间做映射 再32位系统下,每个进程(运行着的程序)的寻找范围是4G,0x00 00 00 00 再写应用程序的,咱们看到的都是虚拟内存 再运行程序的时候,操作系统会将 虚拟内存进行分区 堆 再动态申请内存的时候,再堆里开辟内存 栈 注意存放局部变量(再函数内部,或复合语句内部定义的变量) 静态全局区 未初始化的静态全局区 静态变量(定义变量的时候,前面加static 修饰),或全局变量,没有初始化的,存在此区 初始化的静态全局区 全局变量, 静态变量,赋过初值的,存放在此区 代码区 存放咱们的程序代码 文字常量区 存放常量的 内存以字节为单位来存储数据的。 指针的相关概念 操作系统给每个存储单元分配了一个编号,从0x00 00 00 00 ~0xff ff ff ff 这个编写咱们称之为地址 指针就是地址 指针变量:是个变量,是个指针变量,即这个变量用来存放一个地址编号 再32位平台下,地址总线是32位的,所以地址是32位编号,所以指针变量是32位的4个字节 注意 无论你的指针是什么样子类型的指针,都只有4个字节大小 但是对应类型的指针变量,只能存放对应类型的变量的地址 举例:整型的指针变量,只能存放整型变量的地址 扩展 字符变量 char ch; ch占一个字节,它有一个地址编号,这个地址编号就是ch的地址, 整型变量 int a; a 占4个字节,它占有4个字节的存储单元,有4个地址编号 指针的定义方法 简单的指针 数据类型 * 指针变量名; int *p;//定义了一个指针变量p 在定义指针变量的时候 * 是用来修饰变量的,说明变量p是个指针变量 变量名是p 关于指针的运算符 & 取地址、*取值 列子 int num;p=# num = *p; p保存了 num 的地址,也可以说p指向了num num = *p;//注意:在调用的时候 * 代表取值得意思,* p就相当于p指向的变量 扩展: 如果在一行中定义多个指针变量,每个指针变量前面都需要加*来修饰 int* p,*q;//定义了两个整型的指针变量p,q int * p,q;//定义了一个整型指针变量p,一个整型变量q 如果指针没有赋初值,它的值就是随机的,也就是个野指针 指针的分类 按指针指向的数据的类型来分 字符指针 字符型数据的地址 char *p;//定义了一个字符指针变量,只能存放字符型数据的地址编号 char ch; p = &ch; 短整型指针 short int*p;//定义了一个短整型的指针变量p,只能存放短整型变量的地址 short int a; p = &a; 整型指针 int *p; //定义了一个整型的指针变量p,只能存放整型变量的地址 int a; p =&a; 注意:多字节变量,占多个存储单元,每个存储单元都有地址编号,c语言规定,存储单元编号最小的那个编号,是多字节变量的地址编号 长整型指针 long int*p;//定义了一个长整型的指针变量p,只能存放长整型变量的地址 long int a; p = &a; float 型的指针 float *p;//定义了一个float型的指针变量p,只能存放float型变量的地址 float a; p = &a; double型的指针 double *p; //定义了一个double形的指针变量p,只能存放doube型变量的地址 double a; p =&a; 函数指针 结构体指针 指针的指针 数组指针 总结: 无论什么类型的指针变量,在32位系统下,都是4个字节,只能存放对应类型的数据编号 指针和变量的关系 指针可以存放变量的地址编号 直接通过变量的名称 int a; a = 100; 可以通过指针变量来引用变量 int *p;//在定义的时候, *不是取值的意思, 而是修饰的意思,修饰p是个指针 p = &a;//取a的地址给p赋值,p保存了a的地址,也可以说p指向了a *p = 100;//在调用的时候 *是取值的意思, *指针变量 等价于指针指向的变量 注:指针变量在定义的时候可以初始化 int a; int *p = &a; //用a的地址,给p赋值,因为p是指针变量 指针就是用来存放变量地址的 *+指针变量就相当于指针指向的变量 扩展: 对应类型的指针,只能保存对应类型数据的地址 如果想让不同类型的指针相互赋值的时候,需要强制类型转换 void*p; 通用指针,即可以指向任何类型 注意: *+指针 取值,取几个字节,由指针类型决定的,指针为字符指针则取一个字节,指针为整型指针则取4个字节,指针为double型指针则取8个字节 指针++ 指向下个对应类型的数据 字符指针++,指向下个字符数据,指针存放的地址编号加1 整型指针++,指向下个整型数据,指针存放的地址编号加4 指针和数组元素之间的关系 变量存放在内存中,有地址编号,咱们定义的数组,是多个相同类型的变量的 每个变量都占内存空间,都有地址编号 指针变量当然可以存放数组元素的地址 举例 int a[10]; int *p; p =&a[0]; //指针变量p保存了数组a中第0个元素的地址,即a[0]的地址 数组元素的引用方法 方法1:数组名[下标] int a[10]; a[2] = 100; 方法2 指针名加下标 int a[10]; int *p; p =a; p[2] = 100;// 因为p和a等价 补充:c语言规定:数组的名字就是数组的首地址,即第0个元素的地址 注意:p和a的不同,p是指针变量,而a是个常量。所以可以用等号给p赋值,但不能给a赋值 方法3 通过指针运算符加取值的方法来引用数组的元素 int a[10]; int *p; p = a; *(p+2)=100;//也是可以的 等价于a[2]=100 解释:p是第0个元素的地址,p+2是a[2]这个元素的地址 对第二个元素的地址取值 即a[2] 指针的运算 指针可以加一个整数,往下值几个它指向的变量,结果还是个地址 前提:指针指向数组的时候,加一个整数才有意义 举例 int a[10]; int *p; p =a; p+2;//p是a[0]的地址,p+2是a[2]的地址 假如 p保存的地址编号是2000的话,p+2代表的地址编号是2008 两个相同类型指针可以比较大小 前提:只有两个相同类型的指针指向同一个数组的元素的时候,比较大小才有意义 指向前面元素的指针,小于指向后面元素的指针 举例 int a [10]; int *p, *q,n;//如果在一行上定义多个指针变量的,每个变量名前面加 * p = &a[1]; q = &a[6]; if(p { printf("p }else if(p>q){ printf("p>q \n"); } 两个相同类型的指针可以做减法 前提:必须是两个相同类型的指针指向同一个数组的元素的时候,做减法才有意义 做减法的结果是,两个指针指向的中间有多少个元素 举例 int a[10]; int *p, *q; p = &a[0]; q = &a[3]; printf("%d\n",q-p); 两个相同类型的指针可以相互赋值 注意:只有相同类型的指针才可以相互赋值(void *类型的除外) int *p; int *q; int a; p = &a; //p保存a的地址,p指向了变量a q = p; //用p给q赋值,q也保存了a的地址,指向a 注意:如果类型不相同的指针要想相互赋值,必须进行强制类型转换 注意:c语言规定数组的名字,就是数组的首地址,就是数组第0个元素的地址 int *p; int a[10]; p = a; p =&a[0];//这两种赋值的方法是等价的 指针数组 指针和数组的关系 指针可以保存数组元素的地址 可以定义一个数组,数组中有若干个相同类型指针变量,这个数组被称为指针数组 指针数组的概念 指针数组本身是个数组,是个指针数组,是若干个相同类型的指针变量构成的集合 指针数组的定义方法 类型说明符 * 数组名[元素个数]; int *p[10];//定义了一个整型的指针数组p,有10个元素p[0]~p[9] 每个元素都是int*类型 的变量 int a; p[1] = &a; int b[10]; p[2] = &b[3]; p[2] 和 *(p+2) 是等价的 ,都是指针数组中的第2个元素 指针数组的分类 字符指针数组 char *p[10]、短整型指针数组、整型的指针数组、长整型的指针数组 、float型的指针数组、double型的指针数组 结构体指针数组、函数指针数组 指针的指针 指针的指针,即指针的地址 咱们定义一个指针变量本身指针变量占4个字节,指针变量也有地址编号 n级指针只能存放n-1级指针的地址编号 字符串和指针 字符串的概念 字符串就是以‘\0‘结尾的若干的字符的集合 字符串的存储形式:数组、字符串指针、堆 char string[100] = "I love C!" 定义了一个字符数组string用来存放多个字符,并且用"I love C!"给string数组初始化,字符串“I love C!” 存放在string中 char *str = “I love C!” 定义了一个指针变量str,只能存放字符地址编号, 所以说 I love C! 这个字符串中的字符不能存放在str指针变量中 str只是存放了字符I的地址编号,“I love C!”存放在文字常量区 要用循环的方式去打印这个字符串%c格式输出 也能直接用%s输出,直接传字符串的地址 char *str = (char *)malloc(10 *sizeof(char));//动态申请了10个字节的存储空间,首地址给str赋值 strcpy(str,“I love C”)//将字符串 “I love C!”拷贝到str指向的内存里 字符数组 在内存(栈、静态全局区)中开辟了一段空间存放字符串 字符串指针 在文字常量区开辟了一段空间存放字符串,将字符串的首地址付给str 堆 使用malloc函数在堆区申请空间,将字符串拷贝到堆区 注意 可修改性 栈和全局区内存中的内容是可修改的 char str[100] = "I love C"; str[0] = ‘y‘; //正确可以修改的 文字常量区里的内容是不可修改的 char *str = "I love C!"; *str = ‘y‘; //错误,i存放在文字常量区,不可修改 堆区的内容是可以修改的 char *str=(char *)malloc(10 *sizeof(char)); strcpy(str,"i love c"); *str = ‘y‘;//正确,可以,因为堆区内容是可修改的 注意:str指针指向的内存能不能被修改,要看str指向哪里, str指向文字常量区的时候,内存里的内容不可修改 str指向栈、堆、静态全局区的时候,内存的内容是可以修改 初始化 字符数组、指针指向的字符串:定义时直接初始化 char buf_aver[] = "hello world"; char*buf_point = "hello world"; 堆中存放的字符串不能初始化,只能使用strcpy,scanf赋值 举例 char*buf_heap; buf_heap = (char*)malloc(15); strcpy(buf_heap,"hello world"); scanf("%s",buf_heap); 使用时赋值 字符数组:使用scanf或者strcpy buf_aver = "hello kitty";//错误,因为字符数组的名字是个常量 strcpy(buf_aver,"hello kitty");//正确 scanf("%s",buf_aver);//正确 指向字符串的指针 buf_point = "hello kitty";//正确 buf_point 指向另一个字符串 strcpy(buf_point,"hello kitty");//错误,只读,能不能复制字符串到buf_point指向的内存里,取决于buf_point指向哪里 如果buf_point 指向文字常量区就不能用strcpy 数组指针(一般配合多维数组使用) 二维数组 二维数组,有行,有列.二维数组可以看出有多个一维数组构成的,是多个一维数组的集合,可以认为二维数组的每一个元素是一个一维数组 列子 int a[3] [5]; 定义了一个3行5列的一个二维数组 可以认为二维数组a由3个一维数组构成,每个元素是一个一维数组 回顾 数组的名字是数组 的首地址,是第0个元素的地址,是个常量,数组名字 加1指向下个元素 二维数组a中,a+1指向下个元素,即下一个一维数组,即下一行 数组指针的概念 本身是个指针,指向一个数组,加1跳一个数组,即指向下个数组 数组指针的定义方法 指向的数组的类型 (*指针变量名) [指向的数组的元素个数] int (*p)[5];//定义了一个数组指针变量p,p指向的是整型的有5个元素的数组 p+1 往下指5个整型,跳过一个有5个整型元素的数组 数组指针和指针数组的区别 int(*p)[5];//数组指针 本身是个指针变量,p占4个字节,用来保存数组的地址 int *p[5];//指针数组 本身是个数组,是由5个int*类型的指针元素构成的指针数组 数组指针的用法 具体有例子 各种数组指针的定义 一维数组指针,加1后指向下个一维数组 int(*p)[5]; 配合每行有5个int型元素的二维数组来用 int a[3] [5] p =a; 二维数组指针,加一后指向下个二维数组 int(*p)[4] [5]; 配合三维数组来用,三维数组中由若干个4行5列二维数组构成 int a [3] [4] [5] p = a; 这些三维数组,有个共同的特点,都是有若干个4行5列的二维数组构成 三维数组指针 加1后指向下个三维数组 int(*p)[4] [5] [6] p+1 跳一个三维数组 配合四维数组来使用,四维数组中由若干个[4] [5] [6] 三维数组构成 int a [7] [4] [5] [6] 四维数组指针 加1后指向下个四维数组,以此类推。。。 注意: 容易混淆内容 指针数组:是个数组,有若干个相同类型的指针构成的集合 int *p[10]; 数组p有10个int*类型的指针变量构成,分别是 p[0]~p[9] 数组指针:本身是个指针,指向一个数组,加1跳一个数组 数组名字取地址:变成数组指针 一维数组名字取地址,变成一维数组指针,即加1跳一个一维数组 int a [10]; a+1 跳一个整型元素,是a[1]的地址 a和a+1 相差一个元素 ,4个字节 &a 就变成了一个一维数组指针,是int(*p)[10]类型的 (&a)+1 和&a 相差一个数组 即10个元素 即40个字节 在运行程序时,大家会发现a和&a所代表的地址编号是一样的,即它们指向同一个存储单元,但是a和&a指针类型不同 数组名字和指针变量的区别: int a[10]; int*p; p =a; 相同点 a是数组的名字,是a[0]的地址,p=a即p也保持了a[0]的地址,即a和p都指向a[0],所以在引用数组的时候,a和p等价 引用数组元素回顾 a[2]、*(a+2) 、p[2]、 * (p+2)都是对数组a中a[2]元素的引用 不同点 a是常量 、p是变量 可以用等号‘=‘给p赋值,但是不能用等号给a赋值 对a取地址,和对p取地址结果不同 因为a是数组的名字,所以对a取地址结果为数组指针 p是个指针变量,所以对p取地址(&p) 结果为指针的指针 多维数组中指针的转换 在二维数组中,行地址取*不是取值得意思,而是指针降级得意思,由行地址(数组指针)变成这一行第0个元素得地址,取 *前后还是指向同一个地方,但是指针得类型不一样了 指针和函数的关系 指针作为函数的参数 咱们可以给一个函数传一个整型、字符型、浮点型的数据,也可以给函数传一个地址 列如 int num; scanf("%d",&num); 函数传参: 传数值 void swap (int x, int y) { int temp; temp =x; x =y; y =temp; } int main() { int a= 10,b=20; swap(a,b); printf("a=%d b=%d\n",a,b);//a=10 b =20 } 实参:调用函数时传的参数 形参:定义被调函数时,函数名后边括号里的数据 结论:给被调函数传数值,只能改变被调函数形参的值,不能改变主调函数实参的值 传地址 void swap (int *p1, int *p2) { int temp; temp =*p1; *p1= * p2; //p2指向的变量的值,给p1指向的变量赋值 *p2=temp; } int main() { int a= 10,b=20; swap(&a,&b); printf("a=%d b=%d\n",a,b);//a=20 b =10 } 结论: 调用函数的时候传变量的地址,在被调函数中通过*+地址来改变主调函数中的变量的值 总结一句话:要想改变主函数变量的值,必须传变量的地址,而且还得通过*+地址去赋值。无论这个变量是什么类型的 列如 void fun(char*p) { p = "hello kitty"; } int main() { char*p = "hello world" fun(p); printf("%s \n",p); //结果为 } 案例分析:在fun中改变的是fun函数中的局部变量p,并没有改变main函数中的变量p,所以main函数中的,变量p还是指向 hello world void fun(char**p) { *p = "hello kitty"; } int main() { char*p = "hello world" fun(&p); printf("%s \n",p); //结果为hello kitty } 传数组 给函数传数组的时候,没法一下将数组的内容作为整体传进去,只能传数组的地址 举例 传一维数组的地址 //void fun(int p[]) //形式1 void fun(int *p) //形式2 { printf("%d\n",p[2]); printf("%d\n",*(p+3)); } int main() { int a[10] = {1,2,3,4,5,6,7,8}; fun(a); return 0; } 传二维数组的地址 //void fun(int p [] [4]) //形式1 void fun (int (*p)[4]) //形式2 { } int main() { int a[3] [4] ={{1,2,3,4},{5,6,7,8},{9,10,11,12}}; fun(a); return 0; } 传指针数组 void fun(char **q) //char * q[] { int i ; for(i = 0;i { printf("%s\n",q[i]); } } int main() { char *p[3] ={"hello","world","kitty"};//p[0] p[1] p[2] c fun(p); } 指针作为函数的返回值 一个函数可以返回整型数据、字符数据、浮点型数据,也可以返回一个指针 列如 char *fun() { char str[100] ="hello world"; return str; } int main { char *p;