指针数组初始化赋值_字符数组可以整体赋值吗

指针数组初始化赋值_字符数组可以整体赋值吗c语言基础学习07_指针=======================&

c语言基础学习07_指针   =============================================================================   涉及到的知识点有:   1、指针、指针的概念、指针变量的定义、取地址运算符 &、无类型指针、   指针占用内存的说明、野指针 与 空指针、空指针理解的扩展、指针的兼容性(即指针类型之间一定要匹配)、   不同的数据类型在内存中占用的地址、指向常量的指针 和 指针常量、指针与数组的关系、指针运算、   通过指针使用数组素、不同类型的指针的区别以及与数组的关系、小案例:int类型与ip地址的对应关系   使用指针给二维数组排序、   2、指针数组、二级指针(指向指针的指针)、三级指针及其以上指针、函数的参数为指针变量时(指针变量作为函数的参数)、   函数的参数为数组名时(即数组名作为函数的参数)、函数的返回值为指针时(即指针作为函数的返回值)、   几个c语言的库函数:memset、memcpy、memmove函数,使用的时候需要包含头文件 #include <string.h>、   3、字符指针 与 字符串、通过指针访问字符串数组、通过指针使得字符串逆置、函数的参数为char *(即char *作为函数的参数)、   自定义函数实现求字符串长度和字符串拷贝、例外:如果函数的参数是一个字符串时,那么并不需要再传递一个参数说明这个字符串有多长、   4、指针数组作为main函数的形参、举个小例子:用到main函数的参数,实现计算两个数的和、   课后作业写一个程序,需要用到main函数的参数、   =============================================================================   c语言是面向过程的语言,是弱类型语言,c语言的源代码基本就是无数个函数的堆砌。   即很多函数就组成c语言源代码了,也即它的源代码基本就是函数构成的。   C语言里面的test()和test(void)是不一样的。什么也不写的话,C语言就比较含糊了,容易出错,结果不可知。   C++语言里面的test()和test(void)是一样的。   c语言几个松散的地方(不足的地方,不严禁的地方,它容易出错的地方)。   课后思考:   写一个函数求字符串的长度。课后思考,用递归函数实现求字符串长度。   =============================================================================   指针   指针是c语言里面最抽象的、最重要的、最常用的。   指针的概念:   指针变量也是一个变量,   指针存放的内容是一个地址,该地址指向一块内存空间,   指针是一种数据类型(指针类型)。   计算机内存的最小单位是什么?BYTE(字节)   对于内存,每个BYTE(字节)都有一个唯一不同的编号,这个编号就是内存地址。   操作系统就给内存的每一个字节编了一个号,所以说:一个编号对应的是一个BYTE(字节)的空间大小。   打比方:   1    ->    BYTE   2    ->    BYTE   3   ->    BYTE   4   ->    BYTE   对应于   一个int多大?答:4个BYTE(字节),所以一个int占用了了4个编号(即4个不同的内存地址)。   地址的编号:在32位系统下是一个4个字节的无符号整数;在64位系统下是一个8个字节的无符号整数。   (因为地址不可能是负的,又因为无符号数可以表达一个更大的地址,有符号数表示的最大地址会变小)   指针变量的定义:   可以定义一个指向一个变量的指针变量。   取地址运算符 &   & 可以取得一个变量在内存当中的地址。(取地址取的是内存地址)   register int a; //寄存器变量,这种变量不在内存里面,而在cpu里面,所以是没有地址的,   所以寄存器变量不能使用&来得到地址。   无类型指针   定义一个指针变量,但不指定它指向具体哪种数据类型。可以通过强制转化将 void * 转化为其他类型指针,   也可以用 (void *) 将其他类型强制转化为void类型指针。   void *p; 指针之间赋值需要类型相同,但任何类型的指针都可以赋值给 void * 。   复制代码   1 linux下示例代码如下:   2   3 int main()   4 {   5 int *p; //定义了一个可以指向int类型地址的指针变量,指针变量的名字叫p。*不是指针变量名字的一部分。   6 //int * 是一种数据类型。   7 int a; //定义了一个int类型的变量,int变量的名字叫a。   8   9 a = 1; //int * 和 int是两种不同的数据类型。   10 p = &a; //把a的内存地址赋值给指针变量p。   11   12 printf(“%p   ”, p); //0x7fff5b2faedc 输出的是a的首地址的编号,不会把四个编号都输出的。   13 //而且注意:每一次执行该代码后,输出的编号都会发生变化!   14   15 *p = 10; //通过指针变量间接的访问a的值,*p代表指针指向变量的值,p代表指向变量的地址。   16 printf(“a = %d   ”, a); //a = 10; 通过上面的方法把a的值改变了。   17   18 a = 100;   19 printf(“%d   ”, *p); //100 通过指针变量间接的访问a的值。   20   21 int b = 2;   22 p = &b; //又把b的内存地址赋值给p。   23 *p = 20;   24 printf(“b = %d   ”, b); //20   25   26 //char *p1 = &a; //相当于 char *p1; p1 = &a;//两个类型不相同的地址。即指针类型不兼容。那么我们强转试试!   27 char *p1 = (char *)&a;   28 a = ;   29 *p1 = 0;   30 printf(“a = %d   ”, a); //a = 就算强转后也会出现问题,所以要避免指针类型不兼容问题。   31   32 void *p2; //可以指向任何类型的地址,void代表无类型。   33   34 return 0;   35 }   复制代码   指针占用内存的说明   在同一个系统下,不管指针指向什么样类型的变量,地址的大小(或叫编号的大小)总是一样的。   复制代码   1 linux下示例代码如下:   2   3 int main()   4 {   5 char *p1;   6 int *p2;   7 long long *p3;   8   9 printf(“%lu, %lu, %lu   ”, sizeof(p1), sizeof(p2), sizeof(p3)); //实质是:编号的大小是多少?   10 return 0; //输出的是 8, 8, 8   11 //地址的编号:在32位系统下是一个4个字节的无符号整数;在64位系统下是一个8个字节的无符号整数。   12 //指针变量的名字叫p1、p2、p3。指针变量的大小是多大呢?因为指针变量对应的是某某的首地址的编号,   13 //即指针变量对应的是编号,而编号就是内存地址。即编号在64位系统下是一个8个字节的无符号整数。   14 //所以指针变量的大小就是编号的大小,而编号在64位系统下用8个字节的无符号整数表示。   15 //举例子说明下:同一个酒店,房间的编号的长度都是一样的。   16   17 }   复制代码   再比如:   复制代码   1 linux下示例代码如下:   2   3 #include <stdio.h>   4   5 int main()   6 {   7 int *p1;   8 int a = 0;   9 p1 = &a;   10 *p1 = 10;   11 //p1 = 10;   12   13 int p2;   14 p2 = &a;   15 //p2是什么?不管是p1还是p2都代表变量a的值,但p1和p2确实是两个不同的指针变量。   16 return 0;   17 }   复制代码   画图说明如下:   =============================================================================   野指针 与 空指针   野指针:没有指向任何有效地址的指针变量,所以在代码中避免出现野指针,   如果一个指针不能确定指向任何一个变量地址,那么就将这个指针变成空指针。   复制代码   1 linux下示例代码如下:   2   3 #include <stdio.h>   4   5 int main()   6 {   7 int *p;   8 *p = 100; //不能这样写,没有初始化过值的指针,这种指针叫野指针。   9 return 0; //因为地址编号所占用的内存不是你程序要调用的内存。对于操作系统而言,不是你的内存你就不能改!   10 //如果你非要改的话,操作系统就会发现你在做非法操作,会直接把你清理出去了。即程序出错。   11 }   复制代码   编译上段程序没有错误,运行上段程序会出现一个错误:Segmentation fault(段错误,也即分段故障)   空指针:就是指向了NULL的指针变量。   复制代码   1 linux下示例代码如下:   2   3 #include <stdio.h>   4   5 int main()   6 {   7 int *p; //两句代码相当于一句:int *p = NULL;   8 p = NULL; //如果一个指针变量没有明确的指向一块内存,那么就把这个指针变量指向NULL。   9 //这个指针就是空指针,空指针是合法的。   10 //实际上NULL并不是c语言的关键字,NULL在c语言中的定义是:#define NULL 0   11 //NULL在c语言里面就是一个宏常量,值是0。那么我们为什么不直接写0呢?   12 //NULL代表的是空指针,而不是一个整数零,这样看的会舒服些。(这只是粗浅易懂的解释)   13 return 0;   14 }   复制代码   程序中不要出现野指针,但可以出现空指针。   空指针理解的扩展:   1 注意:   2 int a = 0;   3 int *p = &a; //相当于 int *p; p = &a;   4   5 int *node = NULL; //相当于:int *node; node = NULL;   NULL就是系统定义特殊的0,把你初始化的指针指向它,可以防止“野指针”的恶果。   NULL是个好东西,给一出生的指针一个安分的家。   用C语言编程不能不说指针,说道指针又不能不提NULL,那么NULL究竟是个什么东西呢? C语言中又定义,定义如下:   1 #undef NULL   2 #if defined(__cplusplus)   3 #define NULL 0   4 #else   5 #define NULL ((void *)0)   6 #endif   所以我觉得,如果一个指针被赋予NULL,应该就相当于这个指针执行了0x0000这个逻辑地址,   但是C语言中0x0000这个逻辑地址用户是不能使用的,   (有些人说是因为0x0000没有映射到物理地址,也有人说是因为0x0000映射到的地址是操作系统用于判断野指针的,我也不太懂,总之就是用户不能使用啦)   所以当你试图取一个指向了NULL的指针的内容(或者叫值)时,就会提示段错误,听着有点绕,看程序:   1 int *node = NULL;   2 int a = 0;   3 a = *node; //*node的意思是:取指针变量node的值。然后赋值给a。   4   5 printf(“%d   ”, a);   *node的意思是:取指针变量node的值,也就是逻辑地址0x0000,而这个地址是不能被访问的(即不能被取出来的),   c语言语法上没有问题,所以编译器编译没有问题,但是编译器编译后运行会出现段错误。   复制代码   1 linux下示例代码如下:   2   3 #include <stdio.h>   4   5 int main()   6 {   7 int *p = NULL; //相当于 int *p = 0; 但一般不这么写啊!   8 int a = 0;   9 a = *p;   10 printf(“%d   ”, a);   11   12 return 0;   13 }   14 root@iZ2zeeailqvwws5dcuivdbZ:~/1/01/指针# gcc -o p6 p6.c   15 root@iZ2zeeailqvwws5dcuivdbZ:~/1/01/指针# p6   16 Segmentation fault(段错误,也即分段故障)   17 root@iZ2zeeailqvwws5dcuivdbZ:~/1/01/指针#   复制代码   指针的兼容性(即指针类型之间一定要匹配)   指针之间赋值比普通数据类型赋值检查更为严格,例如:不可以把一个 double * 赋值给int。   复制代码   1 #include <stdio.h>   2   3 int main()   4 {   5 int *p;   6 char b = 1;   7 p = &b; //指针类型之间一定要匹配,不然会有警告,强行运行的话,结果不可控!   8   9 return 0;   10 }   复制代码   警告如下:   warning: assignment from incompatible pointer type [-Wincompatible-pointer-types]   警告:不兼容的指针类型分配[-Wincompatible-pointer-types]   =============================================================================   我们不要把指针想象的特别神秘!其实指针变量也是一个变量。   它里面放的就是一个地址的编号,地址的编号就是一个8个字节的无符号的整数(64位系统下)。   区别是:这个整数不能直接赋值,而是来自于对另外一个变量的取地址操作而得到!   =============================================================================   不同的数据类型在内存中占用的地址   我们先看几个现象:   复制代码   1 linux下示例代码如下:   2   3 #include <stdio.h>   4   5 int main()   6 {   7 char a[10];   8 printf(“%p, %p, %p, %p   ”, a, &a[0], &a[1], &a[2]);   9 return 0;   10 }   11 root@iZ2zeeailqvwws5dcuivdbZ:~/1/01/指针# gcc -o p8 p8.c   12 root@iZ2zeeailqvwws5dcuivdbZ:~/1/01/指针# p8   13 0x7ffe4f449e90, 0x7ffe4f449e90, 0x7ffe4f449e91, 0x7ffe4f449e92   14 root@iZ2zeeailqvwws5dcuivdbZ:~/1/01/指针# gcc -o p8 p8.c   15 root@iZ2zeeailqvwws5dcuivdbZ:~/1/01/指针# p8   16 0x7ffdae4e3c20, 0x7ffdae4e3c20, 0x7ffdae4e3c21, 0x7ffdae4e3c22   17 root@iZ2zeeailqvwws5dcuivdbZ:~/1/01/指针# gcc -o p8 p8.c   18 root@iZ2zeeailqvwws5dcuivdbZ:~/1/01/指针# p8   19 0x7ffd37b6f530, 0x7ffd37b6f530, 0x7ffd37b6f531, 0x7ffd37b6f532   20 root@iZ2zeeailqvwws5dcuivdbZ:~/1/01/指针#   复制代码   每一次编译后执行,输出的地址会发生变化,但是相邻地址间的间隔不变。   再比如:   复制代码   1 linux下示例代码如下:   2   3 #include <stdio.h>   4   5 int main()   6 {   7 int a[10];   8 printf(“%p, %p, %p, %p   ”, a, &a[0], &a[1], &a[2]);   9 return 0;   10 }   11   12 root@iZ2zeeailqvwws5dcuivdbZ:~/1/01/指针# gcc -o p8 p8.c   13 root@iZ2zeeailqvwws5dcuivdbZ:~/1/01/指针# p8   14 0x7ffe9e845ec0, 0x7ffe9e845ec0, 0x7ffe9e845ec4, 0x7ffe9e845ec8   15 root@iZ2zeeailqvwws5dcuivdbZ:~/1/01/指针# gcc -o p8 p8.c   16 root@iZ2zeeailqvwws5dcuivdbZ:~/1/01/指针# p8   17 0x7ffed37c6bc0, 0x7ffed37c6bc0, 0x7ffed37c6bc4, 0x7ffed37c6bc8   18 root@iZ2zeeailqvwws5dcuivdbZ:~/1/01/指针# gcc -o p8 p8.c   19 root@iZ2zeeailqvwws5dcuivdbZ:~/1/01/指针# p8   20 0x7ffdb8b422c0, 0x7ffdb8b422c0, 0x7ffdb8b422c4, 0x7ffdb8b422c8   21 root@iZ2zeeailqvwws5dcuivdbZ:~/1/01/指针#   复制代码   每一次编译后执行,输出的地址会发生变化,但是相邻地址间的间隔不变。   其余的类型就不一一举例啦!   =============================================================================   指向常量的指针 和 指针常量   const int *p;  //定义一个指向常量的指针。   int *const p; //定义一个指针常量,一旦指向某一变量的地址后,不可再指向其他变量的地址。(注意:指针常量也叫常量指针)   二者区别:   const int p; //p是一个变量,但指向一个常量。(即p可以指向任何地址,但是只能通过p来读这块地址的内容,不能通过*p来写这块地址的内容)   int const p; //p是一个常量,但指向一个变量或者常量。(即如果一旦p指向了任何一个有效的地址后,就不可再指向其他变量的地址,但可以通过p来读写这块地址的内容)   复制代码   1 linux下示例代码如下:   2   3 #include <stdio.h>   4   5 int main01()   6 {   7 int a = 0;   8 int p = &a; //此时的p指向了一个int类型的地址,可以通过p的方式来修改这个内存a的值。   9 p = 10;   10 printf(“a = %d   ”, p); //或者printf(“a = %d   ”, a); //此时的p可读可写。   11   12 return 0;   13 }   14   15 int main()   16 {   17 int a = 0;   18 const int p = &a; //此时的p指向了一个int类型的地址,但不可以通过p的方式来修改这个内存a的值。   19 //p = 10;   20 a = 10; //但是呢,不可以通过p来改a的值,可以通过a去修改a的值。   21 printf(“a = %d   ”, p); //或者printf(“a = %d   ”, a); //此时的p可读不可写。   22   23 //c语言的一个小漏洞   24 const int b = 100;   25 //b = 0; //定义了一个常量,那么这个常量权限是只读了。   26   27 //通过指针的方法:即可以通过指向一个变量地址的指针去指向它,然后通过p1去间接的修改b的值。   28 //注意编译的时候会出现警告!我们忽略这个警告强行改!这时把b的值改了!!!   29 //warning: assignment discards ‘const’ qualifier from pointer target type [-Wdiscarded-qualifiers]   30 //警告:赋值时从指针目标类型丢弃“const”限定符[-Wdiscarded-qualifiers]   31   32 //这就是在c语言中用常量的时候不用const了!   33 //因为c语言中的const是有问题的,因为可以通过指针变量间接的修改const定义的常量的值,所以在c语言中用#define定义常量的时候更多。   34   35 //为什么#define不能改呢?实质上#define就是一个文本替换,直接把它替换成一个整数了,整数又不是一个变量。   36 //但是在C++中就没有这个漏洞了。为什么呢?因为c++里面的const是个真的const,而c语言中的const只是在语法的角度不让你去赋值,实际上是假的。   37 //这是c语言本身存在的弱项。   38   39 int *p1;   40 p1 = &b; //为了避免这个warning,使用强转即可:p1 = (int *)&b;   41 *p1 = 0;   42 printf(“b = %d   ”, b); //或者printf(“b = %d   ”, *p);   43   44 int const p2 = &a; //表示p2指向了a的地址,而且p2只能指向a的地址,不可再指向其他变量的地址。   45 //p2 = &b;//直接编译错误//p2是一个指针常量,p2只能指向固定的一个变量的地址,但可以用p2读写这个变量的值。   46   47 return 0;   48 }   复制代码   指针与数组的关系   复制代码   1 linux下示例代码如下:   2   3 #include <stdio.h>   4   5 int main()   6 {   7 int a[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; //数组的名字a就是数组首素的地址。   8 int *p;   9 p = a; //当指针变量指向一个数组的时候,c语言规定指针变量名可以当做数组名使用。   10 //p = a[0]; //二者等价   11   12 a[2] = 200;   13 p[3] = 100; //相当于a[3] = 100;但二者之间也有区别哦!区别如下:   14 printf(“%lu, %lu   ”, sizeof(a), sizeof§);//40, 8 二者所占内存大小不一样。   15   16 int i;   17 for (i = 0; i <10; i++)   18 {   19 //printf(“a[%d] = %d   ”, i, a[i]); //输出没有问题   20 printf(“a[%d] = %d   ”, i, p[i]); //输出也没有问题,完全把指针当数组用!   21 }   22   23 return 0;   24 }   复制代码   一级指针画图小说明如下:   =============================================================================   指针运算   指针变量可以进行计算,如果是 int * 类型每加一,变化4个整数;   如果是 char * 类型每加一,变化1个整数。其他类型以此类推。   linux下示例代码如下:   复制代码   1 #include <stdio.h>   2   3 int main()   4 {   5 int a = 0;   6 int *p = &a;   7 printf(“%p, %p, %p   ”, p, p + 1, p + 2 ); //0x7fff5c2a518c, 0x7fff5c2a5190, 0x7fff5c2a5194   8   9 return 0;   10 }   复制代码   指针运算小例子:   linux下示例代码如下:   复制代码   1 #include <stdio.h>   2   3 int main()   4 {   5 int a[10] = { 0 };   6 int *p = a;   7   8 p += 5;   9 *p = 1;   10   11 p -=2;   12 *p = 3;   13   14 int i;   15 for (i = 0; i < 10; i++)   16 {   17 printf(“a[%d] = %d   ”, i, a[i]);   18 }   19   20 return 0;   21 }   22 输出的是:   23 a[0] = 0   24 a[1] = 0   25 a[2] = 0   26 a[3] = 3   27 a[4] = 0   28 a[5] = 1   29 a[6] = 0   30 a[7] = 0   31 a[8] = 0   32 a[9] = 0   复制代码   通过指针使用数组素   linux下示例代码如下:   复制代码   1 #include <stdio.h>   2   3 int main()   4 {   5 int a[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };   6 int *p = a; //指针p指向a的首地址。   7 p[3] = 100; //等价于 *(p + 3) = 100; 一般写成左边那样。   8   9 printf(“a[%d] = %d   ”, i, a[i]);   10 int i;   11 for (i = 0; i < 10; i++)   12 {   13 }   14   15 return 0;   16 }   复制代码   不同类型的指针的区别以及与数组的关系   极端例子如下:   linux下示例代码如下:   复制代码   1 #include <stdio.h>   2   3 int main()   4 {   5 int a = 0x;   6 char *p = (char *)&a;   7 printf(“%x, %x, %x, %x, %x   ”, *p, p[0], p[1], p[2], p[3]); //%x的意思是按照十六进制的有符号整数输出(小写)   8 printf(“——————–   ”);   9   10 *p = 0;   11 p[3] = 0;   12 printf(“%08x   ”, a);   13 printf(“——————–   ”);   14   15 char b[20] = { 0 };   16 int *p1 = (int *)&b;   17 p1[3] = 0x;   18   19 int i;   20 for (i = 0; i < 20; i++)   21 {   22 printf(“b[%d] = %x   ”, i, b[i]);   23 }   24 printf(“——————–   ”);   25   26 return 0;   27 }   28   29 输出的结果是:   30 78, 78, 56, 34, 12   31 ——————–   32 00   33 ——————–   34 b[0] = 0   35 b[1] = 0   36 b[2] = 0   37 b[3] = 0   38 b[4] = 0   39 b[5] = 0   40 b[6] = 0   41 b[7] = 0   42 b[8] = 0   43 b[9] = 0   44 b[10] = 0   45 b[11] = 0   46 b[12] = 78   47 b[13] = 56   48 b[14] = 34   49 b[15] = 12   50 b[16] = 0   51 b[17] = 0   52 b[18] = 0   53 b[19] = 0   复制代码   说明:小端对齐输出。   小结:c语言中所有的数据类型都可以理解为一个char的数组。   小案例:int类型与ip地址的对应关系。   实际上的ip地址是一个无符号的整数构成的。1个int,4个字节。   1、把整数转换为ip地址   ip地址的格式:   0.0.0.0 ~ 255.255.255.255   linux下示例代码如下:   复制代码   1 #include <stdio.h>   2   3 int main()   4 {   5 unsigned int a = ; //0x 0e 08 47 23 (14.8.71.35) 0e的0可以省略哦!   6 scanf(“�;, &a);   7 unsigned char p = (unsigned char)&a;   8 printf(“懗戅n”, p[3], p[2], p[1], p[0]);   9 printf(“———————   ”);   10   11 int i;   12 for (i = 0; i < 4; i++)   13 {   14 printf(“%2x,%2u   ”, p[i], p[i]);   15 }   16   17 return 0;   18 }   19 输出结果是:   20   21 659   22 108.8.25.251   23 ———————   24 fb,251   25 19,25   26 8, 8   27 6c,108   复制代码   2、把ip地址转换为整数   输入一个ip地址   char a[100] = “192.168.2.5”   把这个ip转化为unsigned int类型的整数。   linux下示例代码如下:   复制代码   1 #include <stdio.h>   2   3 int main()   4 {   5 char a[] = “192.168.2.5”;   6 unsigned int ip = 0;   7 unsigned char *p = (unsigned char *)&ip;   8 int a1, a2, a3, a4;   9   10 sscanf(a, “%d.%d.%d.%d   ”, &a1, &a2, &a3, &a4); //sscanf从某一个格式化字符串中读取到我们想要的东西,找到后通过转义的方式取出来.   11 //printf(“%d.%d.%d.%d   ”, a1, a2, a3, a4);   12   13 p[0] = a4;//p = a4; 二者等价   14 p[1] = a3;   15 p[2] = a2;   16 p[3] = a1;   17   18 /   19 *p = a4;   20 p++;   21 *p = a3;   22 p++;   23 *p = a2;   24 p++;   25 *p = a1;   26 */   27   28 printf(“藜34;, ip);   29   30 return 0;   31   32 }   33 输出结果是:   34   35 root@iZ2zeeailqvwws5dcuivdbZ:~/1/01/指针# gcc -o p16 p16.c   36 root@iZ2zeeailqvwws5dcuivdbZ:~/1/01/指针# p16   37   38 root@iZ2zeeailqvwws5dcuivdbZ:~/1/01/指针# ping   39 PING (192.168.2.5) 56(84) bytes of data.   复制代码   3、使用指针给二维数组排序   linux下示例代码如下:   复制代码   1 #include <stdio.h>   2   3 int main()   4 {   5 char a[2][5] = { { 4, 3, 5, 9, 78 }, { 52, 21, 5, 6, 4 } };   6 char *p = (char *)a;   7   8 int i, j;   9 for (i = 0; i < 10; i++)   10 {   11 for (j =1; j < 10 – i; j++)   12 {   13 if (p[j] < p[j – 1])   14 {   15 char tmp = p[j];   16 p[j] = p[j – 1];   17 p[j – 1] = tmp;   18   19 }   20 }   21 }   22   23 for (i = 0; i < 2; i++)   24 {   25 for (j = 0; j < 5; j++)   26 {   27 printf(“%d   ”, a[i][j]);   28 }   29 }   30   31 return 0;   32   33 }   34   35 输出的结果是:   36 3   37 4   38 4   39 5   40 5   41 6   42 9   43 21   44 52   45 78   复制代码   指针数组   linux下示例代码如下:   复制代码   1 #include <stdio.h>   2   3 int main()   4 {   5 char *a[10]; //定义了一个指针数组,指针数组的名字叫a,每个成员是char *类型,一共10个成员。   6 int *b[10]; //定义了一个指针数组,指针数组的名字叫b,每个成员是char *类型,一共10个成员。   7   8 printf(“%lu, %lu   ”, sizeof(a), sizeof(b)); //80, 80   9   10 int i = 0;   11 //a = &i; //指针数组名不能做左值。   12 //b = &i; //指针数组名不能做左值。   13   14 b[0] = &i; //b[0]的类型是int *。   15 printf(“%lu, %lu   ”, sizeof(b[0]), sizeof(*b[0])); //8, 4 *b[0]是一个数组的成员。   16   17 return 0;   18   19 }   复制代码   linux下示例代码如下:   复制代码   1 #include <stdio.h>   2   3 int main()   4 {   5 int *a[10] = { NULL }; //定义了一个指针数组,指针数组的名字叫a,每个成员是int *类型的,一共有10个成员。   6 int b, c, d; //对于指针数组来说,要先有指针的性质,再有数组的性质,即先得获得地址,然后对数组进行操作。   7   8 a[0] = &b; //a是指针数组的名字。   9 a[1] = &c; //a[0]是指针变量,   10 a[2] = &d;   11   12 *a[0] = 10; //*a[0]是一个数组的成员之一。   13   14 printf(“%d   ”, b);   15   16 return 0;   17 }   复制代码   二级指针(指向指针的指针)   指针是一个变量,既然是变量就也存在内存地址,所以可以定义一个指向指针的指针。   linux下示例代码如下:   复制代码   1 #include <stdio.h>   2   3 int main()   4 {   5 int a = 0;   6 int *p = &a;   7 int pp = &p; //二级指针:指向指针的指针。   8 pp = &p; //pp代表一级指针内存地址的编号。   9   10 //*pp = 1000; //等号左端是指针类型的变量,等号右边是int类型。   11 //或者可以这么理解,指针变量指的是一个地址,无法对它进行赋值。   12 //再或者说,我们两对应的内存地方不一样。操作系统不让你干这非法的事情。(分段错误)   13   14 pp = 10;   15 printf(“%d   ”,a ); //10   16   17 return 0;   18 }   复制代码   二级指针说明图如下:   =============================================================================   三级指针及其以上指针   linux下示例代码如下:   复制代码   1 #include <stdio.h>   2   3 int main()   4 {   5 int a = 0;//零级“指针”,a有内存地址编号,a的内容(值)为0。   6 int *p = &a;//一级指针,p代表a的内存地址编号;*p代表a的内容(值)。   7 int pp = &p;//二级指针,pp代表p的内存地址编号;*pp代表a的内存地址编号;pp代表a的内容(值)。   8 int *ppp = &pp;//三级指针,ppp代表pp的内存地址编号;*ppp代表p的内存地址编号;ppp代表a的内存地址编号,*ppp代表a的内容(值)。   9 int pppp = &ppp;//四级指针   10   11 pppp = 100;   12 printf(“a = %d   ”, a); //100   13   14 return 0;   15 }   16 pppp代表ppp的内存地址编号;   17 *pppp代表pp的内存地址编号;   18 pppp代表p的内存地址编号;   19 *pppp代表a的内存地址编号;   20 pppp代表a的内容(值)。   复制代码   linux下示例代码如下图所示:   特别注意:   能用一级指针解决的问题不要用二级指针,能用二级指针解决的不用三级指针,指针级数过多会导致程序很复杂。   工作中大量使用的是一级指针,二级指针也很常用,三级指针就很罕见了,四级指针几乎没有。但笔试会考你哦!可以画图解决!   =============================================================================   函数的参数为指针变量(指针变量作为函数的参数)   实际上指针更多的时候用在函数的参数上。   函数的参数可以使是指针类型。它的作用是将一个变量的地址编号传送给另一个函数。   void test(int *p); //定义一个函数,形参是int *类型。   c语言中如果想通过在一个函数的内部修改外部实参的值,那么就需要给函数的参数传递这个实参的地址。   linux下示例代码如下:   复制代码   1 #include <stdio.h>   2   3 void swap(int a, int b) //swap是交换的意思。   4 {   5 int tmp = a;   6 a = b;   7 b = tmp;   8 printf(“a = %d, b = %d   ”, a, b);//a = 2, b = 1 形参中的值发生变化了。   9 }   10   11 int main()   12 {   13 int a = 1;   14 int b = 2;   15   16 swap(a, b);   17 printf(“a = %d, b = %d   ”, a, b);//a = 1, b = 2 c语言中实参的值会传给形参,而形参值的改变并不会影响到实参。   18 return 0;   19 }   20 —————————————————————————–   21 那么现在我就想在一个函数的内部修改外部实参的值,那么就需要给函数的参数传递这个实参的地址。代码如下:   22   23 #include <stdio.h>   24   25 void swap(int *a, int *b)   26 {   27 int tmp = *a;   28 *a = *b;   29 *b = tmp;   30 printf(“a = %d, b = %d   ”, a, b);//a = 2, b = 1 形参中的值发生变化了。   31 }   32   33 int main()   34 {   35 int a = 1;   36 int b = 2;   37   38 swap(&a, &b); //那么现在我就想在一个函数的内部修改外部实参的值,那么就需要给函数的参数传递这个实参的地址。   39 printf(“a = %d, b = %d   ”, a, b);//a = 2, b = 1 实参中的值发生变化了。   40 return 0;   41 }   复制代码   函数参数是指针变量的画图说明如下:   即:c语言想通过函数内部来修改实参的值,只能给函数传递实参的地址来间接的修改实参的值。   例如scanf函数:   int a;   scanf(“%d”, &a);//scanf是一个函数,现在要通过函数内部来修改实参a的值,只能用传递a的地址的方式修改a的值   函数的参数为数组名时(即数组名作为函数的参数)   当一个数组名作为函数的形参的时候,c语言将数组名解释为指针变量,其实是一个指针变量名。   如果数组名作为函数的参数,那么这个就不是数组名了,而是一个指针变量名。   当把一个数组名作为函数的参数时,修改形参的值的时候,同时也影响实参的数组成员的值。   如果把一个数组名作为函数的参数,那么在函数内部就不知道这个数组的素个数了,需要再增加一个参数来标明这个数组的大小。   如果将一个数组作为函数的形参进行传递,那么数组的内容可以在被调用函数的内部进行修改,   有时候不希望这样的事情发生,所以要对形参采用const进行修饰。   linux下示例代码如下:   复制代码   1 #include <stdio.h>   2   3 //此三种写法均可:常写的是void test(int a)这一种!   4 //void test(int a[10])   5 //void test(int a[])   6 /   7 void test(int *a)   8 {   9 printf(“%lu   ”, sizeof(a));   10 a[50] = 50;   11 }   12 /   13   14 /   15 //为了从语法的角度不让在函数的内部修改数组成员的值,用const进行限定,如下:   16 void test(const int *a)   17 {   18 printf(“%lu   ”, sizeof(a));   19 //a[5] = 100; 该句编译通不过,出现错误。因为此时的数组只能读,不能改。   20 }   21 */   22   23 //现在我非要在加有const的函数内部进行修改呢?可以,使用指针就可以绕过c语言的语法!   24 void test(const int *a)   25 {   26 printf(“%lu   ”, sizeof(a));   27   28 int *p = (int *)a;   29 p[5] = 50; //此时可以修改了!   30 }   31   32 int main()   33 {   34 int a[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };   35 printf(“%lu   ”, sizeof(a));   36 printf(“———————-   ”);   37   38 test(a);   39   40 int i;   41 for (i = 0; i < 10; i++)   42 {   43 printf(“a[%d] = %d   ”, i, a[i]);   44 }   45   46 return 0;   47 }   复制代码   函数的返回值为指针时(即指针作为函数的返回值)   1 int *test() //函数的返回值类型是指针类型(具体的讲解在下一节:内存管理)   2 {   3 return NULL;   4 }   几个c语言的库函数:memset、memcpy、memmove函数,使用的时候需要包含头文件 #include <string.h>   这三个函数分别实现内存设置、内存复制、内存移动功能。   memset的功能是:将指定区域的内存置空(设置为0)。   void *memset(void *s, int c, size_t n);   第一个参数是:指定要置空的内存的首地址;   第二个参数是:要设置的值,一般写0;   第三个参数是:这块内存的大小,单位:字节。   linux下示例代码如下:   复制代码   1 #include <stdio.h>   2 #include <string.h>   3   4 int main()   5 {   6 int a[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };   7 //想把一个已经初始化的数组成员的值都变成0。   8   9 //法一:传统的方法如下:   10 //a[10] = { 0 };//不能这样来啊,这叫定义后立马进行初始化。   11 /*   12 int i;   13 for (i = 0; i < 10; i++)   14 {   15 a[i] = 0;   16 }   17 */   18   19 //法二:使用c语言库函数memset。   20 memset(a, 0, sizeof(a));   21   22 int i;   23 for (i = 0; i < 10; i++)   24 {   25 printf(“a[%d] = %d   ”, i, a[i]);   26 }   27   28 return 0;   29 }   复制代码   memcpy功能是:两块内存之间拷贝数据。   使用memcpy时,首先一定要确保内存没有重叠区域。   void *memcpy(void *dest, const void *src, size_t n);   第一个参数是:目标地址(目标内存首地址);   第二个参数是:源地址(源内存首地址);   第三个参数是:拷贝多少内容,单位字节。   linux下示例代码如下:   复制代码   1 #include <stdio.h>   2 #include <string.h>   3   4 int main()   5 {   6 //把a的内容拷贝到b中去。   7 int a[10] = { 1, 2, 3, 4 ,5 ,6 ,7 ,8 ,9, 10 };   8 int b[10] = { 0 };   9   10 memcpy(b, a, sizeof(a));   11   12 int i;   13 for (i = 0; i < 10; i++)   14 {   15 printf(“b[%d] = %d   ”, i, b[i]);   16 }   17   18 return 0;   19 }   20 输出的结果是:   21 b[0] = 1   22 b[1] = 2   23 b[2] = 3   24 b[3] = 4   25 b[4] = 5   26 b[5] = 6   27 b[6] = 7   28 b[7] = 8   29 b[8] = 9   30 b[9] = 10   复制代码   linux下示例代码如下:   复制代码   1 #include <stdio.h>   2 #include <string.h>   3   4 int main()   5 {   6 //把a的内容拷贝到b中去。将int改为short。   7 short a[10] = { 1, 2, 3, 4 ,5 ,6 ,7 ,8 ,9, 10 };   8 int b[10] = { 0 };   9   10 memcpy(b, a, sizeof(a));   11   12 int i;   13 for (i = 0; i < 10; i++)   14 {   15 printf(“b[%d] = %d, %08x   ”, i, b[i], b[i]);   16 }   17   18 return 0;   19 }   20 输出的结果是:   21 b[0] = , 00020001   22 b[1] = , 00040003   23 b[2] = , 00060005   24 b[3] = , 00080007   25 b[4] = , 000a0009   26 b[5] = 0, 00000000   27 b[6] = 0, 00000000   28 b[7] = 0, 00000000   29 b[8] = 0, 00000000   30 b[9] = 0, 00000000   复制代码   内存拷贝说明画图如下:   memmove功能是:内存移动,参数与memcpy一致。   void *memmove(void *dest, const void *src, size_t n);   第一个参数是:目标地址(目标内存首地址);   第二个参数是:源地址(源内存首地址);   第三个参数是:拷贝多少内容,单位字节。   内存重叠区域说明如下图所示:   =============================================================================   指针小结   定义      说明   int i;      定义个一个int类型的变量   int *p;      定义一个指向int类型的指针变量   int a[10];    定义一个int类型的数组   int *p[10];    定义一个指针数组,其中每一个数组素指向一个int类型变量的地址   int func();    定义一个函数,返回值类型为int类型   int func();    定义一个函数,返回值类型为int类型   int p;      定义一个指向int类型的指针的指针,二级指针   =============================================================================   字符指针 与 字符串   在c语言中,大多数的字符串操作其实就是指针操作。   1、通过指针访问字符串数组   linux下示例代码如下:   复制代码   1 #include <stdio.h>   2   3 int main()   4 {   5 char a[100] = “hello world”;   6 char *p = a;   7   8 *p = ‘a’; //注意:*p = p[0]   9 p[3] = ‘b’;   10 printf(“%s   ”, a); //aelbo world   11   12 return 0;   13 }   复制代码   通过指针使得字符串逆置   法一:使用一个指针   linux下示例代码如下:   复制代码   1 #include <stdio.h>   2 #include <string.h>   3   4 int main()   5 {   6 char a[100] = “hello world”;   7 char *p = a;   8   9 int len = strlen(a);   10 int min = 0;   11 int max = len – 1;   12   13 while (min < max)   14 {   15 //char tmp = *p;//等价于tmp = p[0];   16 char tmp = *(p + min);   17 *(p + min) = *(p + max);   18 *(p + max) = tmp;   19 max–;   20 min++;   21 }   22   23 printf(“%s   ”, a); //dlrow olleh   24   25 return 0;   26 }   复制代码   法二:使用二个指针   linux下示例代码如下:   复制代码   1 #include <stdio.h>   2 #include <string.h>   3   4 int main()   5 {   6 char a[100] = “hello world”;   7 char *p = a; //等价于 char *p; p = a;//p指向了数组的首素的地址。   8 char *p1 = a; //等价于 char *p1 = p;   9   10 int len = strlen(a);   11   12 p1 += len – 1;//p1指向了数组的最后一个素的地址。   13   14 while (p < p1)   15 {   16 char tmp = *p;   17 *p = *p1;   18 *p1 = tmp;   19 p++;   20 p1–;   21 }   22   23 printf(“%s   ”, a); //dlrow olleh   24   25 return 0;   26 }   复制代码   函数的参数为char *(即char *作为函数的参数)   linux下示例代码如下:   复制代码   1 #include <stdio.h>   2   3 //三者形式等价,钟爱第三种!   4 //void test(char a[10])   5 //void test(char a[])   6 void test(char *a) //数组作为函数的形参时,相当于一级指针。   7 {   8 printf(“%s   ”, a);   9 a[3] = ‘4’;   10 }   11   12 int main()   13 {   14 char a[] = “abcd”;   15 test(a);   16 printf(“%s   ”, a);   17   18 return 0;   19 }   20 输出结果是:   21 abcd   22 abc4   复制代码   如果将一个数组作为函数的形参进行传递,那么数组的内容可以在被调用函数的内部进行修改,   有时候不希望这样的事情发生,所以要对形参采用const进行修饰。代码如下:   1 void test(const char *a)   2 {   3 printf(“%s   ”, a);   4 //a[3] = ‘4’;   5 }   自定义函数实现求字符串长度和字符串拷贝   linux下示例代码如下:   复制代码   1 #include <stdio.h>   2   3 int mystrlen(const char *s)   4 {   5 int len = 0;   6 while (s[len])   7 {   8 len++;   9 }   10 return len;   11 }   12   13 void mystrcpy(char *s1, const char *s2)   14 {   15 int len = 0;   16 while (s2[len])   17 {   18 s1[len] = s2[len];   19 len++;   20 }   21 }   22   23 int main()   24 {   25 char a[10] = “abc”;   26 char b[10] = { 0 };   27   28 int i = 0;   29 i = mystrlen(a);   30 mystrcpy(b, a);   31   32 printf(“%d   ”, i);   33 printf(“%s   ”, b);   34 return 0;   35 }   36 输出的结果是:   37 3   38 abc   复制代码   如果一个数组作为函数的参数,那么数组的成员数量在函数内部是不可见的。   解决方法:在传递一个数组的时候,需要同时提供另外一个参数,标明这个数组有几个成员变量。   例外:如果函数的参数是一个字符串时,那么并不需要再传递一个参数说明这个字符串有多长。   linux下示例代码如下:   复制代码   1 #include <stdio.h>   2   3 int printf_array(int n, int *a)   4 {   5 int i;   6 for (i = 0; i < n; i++)   7 {   8 printf(“%d   ”, a[i]);   9 }   10 }   11   12 int main()   13 {   14 int a[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };   15 printf_array(sizeof(a) / sizeof(a[0]), a);   16   17 return 0;   18 }   19   20 注意:size_t 单位是1个字节。   复制代码   指针数组作为main函数的形参   先来看一个指针数组作为函数的参数(此时把指针数组解释为二级指针)   linux下示例代码如下:   复制代码   1 #include <stdio.h>   2   3 //int print(char *p[10])   4 //int print(char *p[])   5 int print(int n, char p) //指针数组作为函数的形参时,相当于二级指针。   6 {   7 int i;   8 for (i = 0; i < n; i++)   9 {   10 printf(“%s   ”, p[i]); //p[0]、p[1]、p[1]等的类型是char *。   11 printf(“%c   ”, *p[i]); //*p[0]、*p[1]等的类型是char。   12 }   13 }   14   15 int main()   16 {   17 char *a[4]; //a[0]、a[1]、a[2]、a[3]分别指向一个char *类型的数组。   18   19 char a1[] = “hello”;   20 char a2[] = “abc”;   21 char a3[] = “world”;   22 char a4[] = “haha”;   23   24 a[0] = a1;   25 a[1] = a2;   26 a[2] = a3;   27 a[3] = a4;   28   29 printf(“%lu, %lu   ”, sizeof(a), sizeof(a[0]));   30   31 print(sizeof(a) / sizeof(a[0]), a);   32   33 return 0;   34   35 }   36 输出结果是:   37 32, 8   38 hello   39 h   40 abc   41 a   42 world   43 w   44 haha   45 h   复制代码   linux下示例代码如下:   复制代码   1 #include <stdio.h>   2   3 int main(int argc, char args)//argc代表这个数组有多少成员,args是一个指针数组,args这个数组的每个成员类型是char *,   4 {   5 int i;   6 for (i = 0; i < argc; i++)   7 {   8 printf(“%s   ”, args[i]);   9 }   10   11 return 0;   12 }   复制代码   args是命令行参数的字符串数组,argc代表命令行参数的数量,程序名字本身就算一个参数。   main函数是由系统调用的,所以main函数的参数功能是:得到命令行的参数。   举个小例子:用到main函数的参数,实现计算两个数的和   例如:   程序名 数1 数2   一回车结果就出来了。   a 15 45   60   linux下示例代码如下:   复制代码   1 #include <stdio.h>   2 #include <stdlib.h>   3   4 int main(int argc, char args) //注意:main函数的参数的类型、和顺序是不能修改的。   5 {   6 if (argc <= 2)   7 {   8 printf(“参数不足,使用方法:%s 整数1 整数2   ”, args[0]);   9 return 0;   10 }   11 else   12 {   13 int a = atoi(args[1]);   14 int b = atoi(args[2]);   15 printf(“%d   ”, a + b);   16 }   17   18 return 0;   19 }   复制代码   课后作业   写一个程序,需要用到main函数的参数   例如:   程序名 整数1 运算符 整数2,程序运行的结果是计算结果。   a 5 + 6    注意:中间的加号是字符串。   11   a 5 * 6   30   … / 都要实现   linux下示例代码如下:   复制代码   1 #include <stdio.h>   2 #include <stdlib.h>   3   4 int main(int argc, char args) //等价于 char args[]   5 {   6 //因为星号在linux下是一个通配符,代表在当前目录下的所有文件。   7   8 /   9 //验证星号是什么?   10 int i;   11 for (i = 0; i < argc; i++)   12 {   13 printf(“%s   ”, argc[i]);   14 }   15 return 0;   16 */   17   18 if (argc < 4)   19 {   20 return 0;   21 }   22   23 //因为 char args,是指针数组,args是二级指针,分别是 args[0]、args[1]等,类型是char *。   24 int a = atoi(args[1]); //把第一个参数转化为int。   25 int b = atoi(args[3]); //把第三个参数转化为int。   26   27 //“+” 中间的加号是一个字符串。该字符串是一个字符数组,且对于该字符串的加号是第一个素。   28 char *s = args[2]; //得到第二个参数,因为每个参数的类型都是char 。   29 char c = s[0];   30 //char c = args[2][0]; //该句话与上面两句话等价。   31   32 switch ©   33 {   34 case ‘+’:   35 printf(“%d   ”, a + b);   36 break;   37 case ‘-’:   38 printf(“%d   ”, a – b);   39 break;   40 case ‘’:   41 printf(“%d   ”, a * b);   42 break;   43 case ‘/’:   44 printf(“%d   ”, a / b);   45 break;   46 default:   47 printf(“error   ”);   48 }   49   50 return 0;   51 }   52 执行结果为:   53 “p35.c” 51L, 1226C written   54 root@iZ2zeeailqvwws5dcuivdbZ:~/1/01/指针# gcc -o a p35.c   55 root@iZ2zeeailqvwws5dcuivdbZ:~/1/01/指针# a 6 + 5   56 11   57 root@iZ2zeeailqvwws5dcuivdbZ:~/1/01/指针# a 6 – 5   58 1   59 root@iZ2zeeailqvwws5dcuivdbZ:~/1/01/指针# a 6 * 5 (注意要有转义字符)   60 30   61 root@iZ2zeeailqvwws5dcuivdbZ:~/1/01/指针# a 6 / 5   62 1   63 root@iZ2zeeailqvwws5dcuivdbZ:~/1/01/指针# a   64 root@iZ2zeeailqvwws5dcuivdbZ:~/1/01/指针#   复制代码

2024最新激活全家桶教程,稳定运行到2099年,请移步至置顶文章:https://sigusoft.com/99576.html

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。 文章由激活谷谷主-小谷整理,转载请注明出处:https://sigusoft.com/33829.html

(0)
上一篇 2024年 9月 10日 下午4:18
下一篇 2024年 9月 10日

相关推荐

关注微信