目录

第七章 指针

第1课:函数复习

第2课:指针的含义与定义

第3课:指针的兼容性

第4课:指针操作实现计算字符串长度以及合并字符串

第5课:游戏外挂

第6课:指针运算

第7课:用指针实现数组逆置 以及 求数组最大值元素。

第8课:指针实现获取数组第二大元素

第9课:指针实现汉字字符串逆置

计算机科学与技术 & 计算机网络技术:双专业课程体系完全导航指南


第七章 指针

第1课:函数复习

main函数与exit函数

       main函数中调用return和调用exit的效果是一样的。

       在子函数中调用exit代表整个程序退出了,但调用return只是这个子函数退出,程序主体还在运行。

  exit(0);                                                                  

  return 0;

第2课:指针的含义与定义

指针的概念

       指针也是一个变量。

       指针存放的内容是一个地址,该地址指向一块内存空间。    

计算机的内存最小单位是Byte(每个Byte8个位(bit)),每个Byte的内存都有一个唯一的编号,这个编号就是内存地址。编号在32位的系统下一个32位的整数,在64位系统下是一个64位的整数。

案例:获取变量的内存地址

int main(){

       int a=5;

       int b=10;

       char s[10]="hello";

       printf("a=%p,b=%p,s=%p\n",&a,&b,s);

       return 0;

结果为:

注释:%p表示指针。ppointer的意思。&是取变量地址的意思

指针变量的定义

       int *p;   定义一个指针变量,名字叫p,它可以指向一个int的地址。

*p代表指针所指内存的实际数据。切记,指针变量只能存放地址,不能直接将一个int型变量赋值给一个指针(int *p=100;这样是不行的)。

       int a=10;

       int *p1=&a; //得到变量a的地址,并将这个地址赋值给变量p1

//地址虽然是个整数,但地址是一个特殊的整数,是不能直接通过整数来操作的。

       int *p2;

       int b=10;

       p2=&b;        //指针变量的值一般不能直接赋值一个整数,而是通过取变量地址的方式赋值

       *p代表指针所指内存的实际数据,案例如下:

int main(){

       int a=5;

       int *p;

       p=&a;

       int b = *p;    //*p代表指针p处的数据。

       printf("%d\n",b);

       *p=10;         //通过指针修改指针指向变量的值。

       printf("a=%d\n",a);

       return 0;

}

简而言之:

       1定义指针用 *p

       2p代表指针地址。

       3*p代表地址处的数据。

       4、初始化指针时,只能用 &a赋值,不能用a赋值,即:int *p=&a;

       5、已定义的指针p,可以用a赋值*p,也可以用&a赋值p,如:

       p=&a    *p=a; 

案例:查看char数组的内存地址

int main(){

       char s[10];

       printf("%X,%X,%X,%X\n",s,&s[0],&s[1],&s[2]);

       printf("&s[0]=%p\n",&s[0]);

       printf("s=%p\n",s);

       printf("&s=%p\n",&s);

       return 0;

}

结果如下:

从上看出,对于char数组s[10]

       1s 表示的就是地址,s 等同于 &s

       2s[1]s[2]s[3]...表示数据

       3& s[0]& s[2]& s[1]...表示址

       4s 等于&s[0]                                                 

       5char数组成员间相隔1个字节。

案例:查看int数组的内存地址

int main(){

       int i[10];

       printf("%X,%X,%X,%X\n",i,&i[0],&i[1],&i[2]);

       printf("&i[0]=%p\n",&i[0]);

       printf("i=%p\n",i);

       printf("&i=%p\n",&i);

       return 0;

}

效果如下:

从上看出,对于int数组i[10]

       int数组成员间相隔4个字节。

&取地址运算符

       &可以取得一个变量在内存当中的地址。

无类型指针

       定义一个指针变量,但不指定它指向具体哪种数据类型,可以通过强制转化符 void * 转化为其它类型指针,也可以用 void * 将其它类型指针强制转化为 void 类型指针。

char c=0;

char *p=&c;  //char 类型的指针 

viod *p   //这叫无类型指针,意思是它只是一个指针变量,而不指定具体的数据类型。      

指针占用的字节数(32位下4个字节)

int main(){

       int *p1;

       char *p2;

       void *p3;

       printf("p1=%d,p2=%d,p3=%d\n",sizeof(p1),sizeof(p2),sizeof(p3));

       return 0;

}

结果为:

NULL

void *p;

p=NULL;  //将指针赋值NULL,值为NULL的指针,俗称空指针。

空指针与野指针

       指向NULL的指针叫空指针

       没有指向任何变量地址的指针叫野指针

       int *p //因未赋值,p就是个野指针

   案例:野指针

              int main(){

                     int a=10;

                     int *p;

                     printf("%X\n",p);

                     return 0;

}

       结果为(会报错):

       可见野指针,指向的地址是CCCCCCCC

       程序中要避免野指针的存在,因为野指针是导致程序崩溃的主要原因。要用野指针时,一定要先给它赋值

       空指针是合法的,野指针是危险的。      

案例:空指针

              int main(){

                     int a=10;

                     int *p;

                     p=NULL;

                     printf("%X\n",p);

                     return 0;

}

         结果为:

       可见 空指针,地址的值为0

指针地址与指针处数据是绑定在一起的

       修改指针地址,指针数据相应改变

       修改指针数据,指针地址所对应变量值相应改变。

案例:

       int main(){

       int a=10;

       int b=20;

       int *p=&a;

       *p=60;          //修改指针处数据

       printf("a=%d\n",a);

       p=&b;          //修改指针地址

       printf("*p=%d\n",*p);

       return 0;

}

结果为:

第3课:指针的兼容性

指针的兼容性

       指针之间赋值比普通数据类型赋值检查更为严格,例如:不能把 double *p赋值给int *p1

       原则上是,相同类型的指针指向相同类型的变量地址,不能用一种类型的指针指向另一种类型的变量地址。

int a=0x1312;表示a等于16进制数1312

为什么用0x表示16进制?

因为:x表示16进制,但数值不能以字母开头,所有在前面加一个0 

案例:把int 类型变量地址赋值给char类型的指针

int main(){

       int a=0x4321;

       char *p=&a;

       printf("*p=%X\n",*p);      

       return 0;

}

结果为:*p=21

案例:把char 类型变量地址赋值给int类型的指针

int main(){

       char b='a';

       int *p=&b;

       printf("*p=%X\n",*p);      

       return 0;

}

结果为:*p=CCCCCC61

案例:把char 类型数组变量地址赋值给int类型的指针

int main(){

       char b[]={1,2,3,4,5,6,7,8};

       int *p=b;

       printf("*p=%X\n",*p);      

       return 0;

}

结果为:*p=04030201,如下:

案例:把float 类型变量地址赋值给int类型的指针

int main(){

       float a=3.14;

       int i=a;         //自动数据类型转化,将浮点数的小数部分舍弃

       int *p=&a;   //严重错误,因为指针类型不兼容

       printf("*p=%X\n",*p);      

       return 0;

}

结果为:*p=4048F5C3

指向常量的指针与指针常量

       const char *p;  //定义一个指向常量的指针

      char *const p;  //定义一个指针常量,一旦初始化后其内容不可改变。

案例:指向常量的指针

int main(){

       int a=10;

       int b=20;

       const int *p=&a//p原则上只能指向一个常量,实在要指向变量的话,也只能读取数据,不能修改数据。

       //*p=20;              //不能通过*p的方法修改一个const指针的数据

       printf("*p=%d\n",*p);

       p=&b;          //可以修改指向常量的指针的地址。

       printf("*p=%d\n",*p);

       return 0;

}

结果为:

*p=10

*p=20

案例:指针常量

int main(){

       int a=10;

       int b=20;

       int *const p=&a; //定义一个常量指针,可以通过常量指针读取或修改一个变量的值

       *p=100;

       printf("*p=%d\n",*p);

       //p=&b;        //报错,常量指针一旦定义了,就不能修改其指向的变量地址。      

       return 0;

}

结果为:*p=100

指针与数组的关系

char类型的变量只能保存 -128127之间的数可见char是个有符号数

补充:上面说法不对,之所以只能保存-128127之间的数,是由于定义的问题,如果定义为unsigned char就可以保存0255之间的数,(char a='a'等同于signed char 'a'

案例:unsigned char signed char

int main(){

              char a=0;

              a=200;

              printf("%d\n",a);

              return 0;

}

   结果为:-56

int main(){

              unsigned char a=0;

              a=200;

              printf("%d\n",a);

              return 0;

}

结果为:200

       10000000-1281111111-127(原码)01111111127000000000

案例:通过指针偏移修改char数组的值

int main(){

       char s[10]={0,1,2,3,4,5};

       char *p=s;          //指向数组s的第一个元素的地址

       int i;

       for(i=0;i<10;i++){

             *p=i*i;       //给指针指向地址处赋值

            p++;          //指针偏移1,指向数组s的下一元素的地址

       }      

       for(i=0;i<10;i++){

              printf("s[%d]=%d,",i,s[i]);

       }

       return 0;

}

结果为:

当指针p指向数组s的最后一个成员后,不能再用p++进行偏移,否则程序会崩溃。此时若要再次修改数组s的值,应该先把指针p还原回s处:

   p=s;  p=&s  或 p=&s[0]

思考1个问题:

printf("%d\n",s),得到的是什么?

答:是地址&s[0]10进制形式打印出来。

案例:用指针保存IP地址

       IP地址在写法上是一个字符串,如“192.168.65.30”,但IP在内存中保存时实际上是一个Dword型数,占4个字节。在32位系统中等同于int

       IP地址为什么不保存为字符串形式呢?因为太浪费字节。如果用int来传递IP地址,则4个字节足够了。

int main(){

int ip=0;

       unsigned char *p=&ip;

       *p=192;

       p++;

       *p=168;

       p++;

       *p=30;

       p++;

       *p=60;

       p=&ip;         //让指针复位

       printf("%d\n",ip);

       printf("%u.%u.%u.%u\n",*p,*(p+1),*(p+2),*(p+3));

       return 0;

}

效果为:

【上面案例用gcc编译时报错,在VS中不报错】

C语言中,允许指针通过数组下标的方法访问数组成员,即:

*p,*(p+1),*(p+2),*(p+3) 等同于p[0],p[1],p[2],p[3]

案例:把一个字符串形式的IP地址(“192.168.2.3 )转化为IP地址

#include <stdio.h>

int main(){

       char s[100]="192.168.2.3";

       int a=0;

       int b=0;

       int c=0;

       int d=0;      

       int ip=0;              //如果把ip*p定义在printfsscanf后则出错

       unsigned char *p = &ip;  

       sscanf(s,"%d.%d.%d.%d",&a,&b,&c,&d);

       printf("a=%d,b=%d,c=%d,d=%d\n",a,b,c,d);

       *p=a;

       p++;

       *p=b;

       p++;

       *p=c;

       p++;

       *p=d;

       p=&ip;

       printf("ip为:%u.%u.%u.%u\n",*p,*(p+1),*(p+2),*(p+3));

       return 0;

}

结果为:

C语言中,允许指针通过数组下标的方法访问数组成员,即:

*p,*(p+1),*(p+2),*(p+3) 等同于 p[0], p[1], p[2], p[3]

【上面案例用gcc编译时报错,在VS中不报错】

发现一个问题:在vs中,变量定义在printfsscanf后时,程序出错,如下:

int main(){

       printf("aa");

       int a;

}

vs中执行上面代码时,错误如下:

第4课:指针操作实现计算字符串长度以及合并字符串

案例1:用指针来求一个字符串的长度,不可以使用数组下标的方式

代码如下:

int main(){

       char s[100]="hello world";

       int len=0;

       char *p=s;

      while(*p){       //*p的内容为0时,表示字符串结束了

            len++;

            p++;

      }

       printf("len=%d\n",len);      

}

结果为:len=11

案例2:用指针将两个字符串合并,不可以使用数组下标的方式

int main(){

       char s1[100]="hello world ";

       char s2[100]="123456";

       int len=0;

       char *p=s1;

       while(*p){

              len++;

              p++;

       }

       char *p2=s2;

       while(*p2){

              *p=*p2;

              p++;

              p2++;

       }

       printf("len=%d\n",len);

       printf("%s\n",s1);      

}

效果如下:

上面代码在VS中不行,用GCC可以

*p=*p2;

p++;

p2++;

可以简写为*p++=*p2++

注意(*p)++=(*p2)++ *(p++)=*(p2++) 所表示的不同含义

第5课:游戏外挂

此处省略一万字......

第6课:指针运算

       指针运算不是简单的整数加减法,而是指针指向的数据类型在内存中占用字节数作为倍数来运算的。如:

       char *p;

       p++; 移动了sizeof(char)这么多的字节

       int *p1;

       p++; 移动了sizeof(int)这么多的字节

       p+=3; 移动了3*4=12个字节。

案例:p++p+=2移动了多少位置

int main(){

       int buf[20];

       int *p=buf;

       printf("p=%X\n",p);

       p++;      //移动了4个字节

       printf("p=%X\n",p);

       p+=2;    //移动了8个字节

       printf("p=%X\n",p);

       return 0;

}

效果如下:

如果要简单地加减法对p进行移动,就得把p类型进行强制转化,案例如下:

案例:强制转化指针p的类型,以进行简单加减运算

int main(){

       int buf[20];

       int *p=buf;

       printf("p=%X\n",p);

       p=(int)p+2;    //强制转化指针p的类型为int数据型

       printf("p=%X\n",p);

       return 0;

}

结果为:

案例:用char *p 指针指向int类型变量数组并移动指针。

int main(){

       int buf[20];

       char *p=buf;

       printf("p=%X\n",p);

       p+=4;           // 移动了4个字节

       printf("p=%X\n",p);

       return 0;

}

效果如下:

案例:两个指针相减是什么结果

int main(){

       int buf[20];

       int *p1=buf;

       int *p2=&buf[2];

       printf("p2-p1=%d\n",p2-p1);  //得到数组元素的相对距离

       printf("(int)p2-(int)p1=%d\n",(int)p2-(int)p1); //得到相隔字节数。

       return 0;

}

结果为:

案例:long long类型指针相减是什么结果(long long类型为8个字节)

int main(){

       int buf[20];

       long long *p1=buf;

       long long *p2=&buf[2];

       printf("p2-p1=%d\n",p2-p1);

       return 0;

}

结果为:

案例: long类型指针相减是什么结果(long类型为4个字节,与int相同)

..省略..

案例:short类型指针相减是什么结果(short类型为2个字节)

int main(){

       int buf[20];

       short *p1=buf;

       short *p2=&buf[2];

       printf("p2-p1=%d\n",p2-p1);

       return 0;

}

结果为:

求差值,p2-p1,通常用于同一个数组内求两元素之间的距离

比较 p1==p2,通常用来比较两个指针是否指向同一位置

案例:用指针实现数组从小到大排序,并用指针遍历数组且打印出来。

#include <stdio.h>

void sort_buf(int *p){

       int i,j;

       for(i=0;i<10;i++){

              for(j=1;j<10-i;j++){

                     if(*(p+j)<*(p+j-1)){

                            int tmp=*(p+j-1);

                            *(p+j-1)=*(p+j);

                            *(p+j)=tmp;

                     }

              }

       }

}

void print_buf(int *p){

       int i=0;

       for(i=0;i<10;i++){

              printf("%d,",*(p+i));

       }

}

int main(){

       int buf[10]={50,26,96,45,12,36,45,78,27,63};

       int *p=buf;

       sort_buf(p);

       print_buf(p);

       return 0;

}

效果如下:

注释:为什么j1开始?(j=1;j<10-i;j++

解释:如果是从0开始,即(j=0;j<10-i;j++),那么,if条件就应该是:

if(*(p+j)<*(p+j+1)),当j为9时p+j+1超出数组范围

第7课:用指针实现数组逆置 以及 求数组最大值元素。

案例:取数组最大值元素

#include <stdio.h>

int max_buf(int *p){

       int value=*p;

       int i;

       for(i=1;i<10;i++){

              if(value<*(p+i)){

                     value=*(p+i);

              }

       }

       return value;

}

int main(){

       int buf[10]={50,26,96,45,12,36,45,78,27,63};

       int *p=buf;

       int i=max_buf(p);

       printf("%d\n",i);

       return 0;

}

结果为:96

案例:数组逆置

#include <stdio.h>

void print_buf(int buf[]){

       int i;

       for(i=0;i<10;i++){

              printf("%d,",buf[i]);

       }

}

int main(){

       int buf[10]={50,26,96,45,12,36,45,78,27,63};

       int *start=&buf[0];

       int *end=&buf[9];

       print_buf(buf);      

       while(start<end){

              int tmp=*start;

              *start=*end;

              *end=tmp;

              start++;

              end--;

       }

       printf("\n");

       print_buf(buf);

       return 0;

}

结果为:

第8课:指针实现获取数组第二大元素

案例:在不对数组排序的条件下,求数组第二大元素,并且不能用数组下标,只能用指针。

方法1

#include <stdio.h>

int main(){

       int buf[10]={50,26,96,45,12,36,45,78,27,63};

       int *p=buf;

       int i;

       int big1=*p;

       int big2=*p;

       for(i=1;i<10;i++){

              if(big1<*(p+i))

                     big1=*(p+i);

       }

       for(i=1;i<10;i++){

              if(big2<*(p+i) && *(p+i)!=big1)

                     big2=*(p+i);

       }

       printf("big1=%d,big2=%d\n",big1,big2);

       return 0;

}

结果为:

方法2

#include <stdio.h>

int main(){

       int buf[10]={50,26,96,45,12,36,45,78,27,63};

       int *p=buf;

       int i;

       int big1=*p;

       int big2=*p;

       for(i=1;i<10;i++){

              if(big1<*(p+i)){

                     big2=big1;

                     big1=*(p+i);

              }else if(big1>*(p+i) && big2<*(p+i))

                     big2=*(p+i);

       }

       printf("big1=%d,big2=%d\n",big1,big2);

       return 0;

}

结果为:

方法2思路解析:

第9课:指针实现汉字字符串逆置

方法1:

#include <stdio.h>

int main(){

       char buf[100]="你好我也好";

       char *p=buf;

       int len=0;

       while(*(p++)){

              len++;

       }

       len--;     //勿忘 --,上面字符串长度是10,但编号却是09

      char *start=buf;

       char *end=buf;

       end+=len;

       while(start<end){

              char tmp=*start;

              *start=*(end-1);

              *(end-1)=tmp;

              char tmp2=*(start+1);     //千万别用char tmp,定义同名变量,会报错

              *(start+1)=*end;

              *end=tmp2;

              start+=2;

              end-=2;

       }

       printf("%d\n",len);

       printf("%s\n",buf);

       return 0;

}

结果为:

【上面代码在VS中报错,GCC中正常】

方法2

#include <stdio.h>

#include <string.h>

int main(){

       char str[100]="你好我也好";

       short *start=&str[0];

       short *end=&str[strlen(str)-2];      

       while(start<end){

              short tmp=*start;

              *start=*end;

              *end=tmp;

              start++;

              end--;

       }

       printf("%s\n",str);

       return 0;

}

效果如下:


计算机科学与技术 & 计算机网络技术:双专业课程体系完全导航指南

 本系列目录

1、回看经典!第一章 从“Hello World”到理解编译本质(2015年C语言培训班笔记重读)

2、回看经典!第二章 数据类型与运算符详解(2015年C语言培训班笔记重读)

3、回看经典!第三章 流程控制全解 逻辑/分支/循环/图形实战(2015年C语言培训班笔记重读 )

4、回看经典!第四章 · C语言数组与字符串核心实战 从定义、遍历到排序与算法详解(2015年C语言培训班笔记重读)

5、回看经典!第五章 C语言数组高级应用实战:字符串处理、安全防范与多文件编程(2015年C语言培训班笔记重读)

6、回看经典!第六章 C语言综合能力提升:多文件编程与递归函数核心解析及实战(2015年C语言培训班笔记重读)

7、回看经典!第七章 全面解析C语言指针:从核心概念到高效应用(2015年C语言培训班笔记重读)

8、回看经典!第八章 C语言指针完全指南:深度解析二维数组、多级指针与内存操作实战(2015年C语言培训班笔记重读)

9、回看经典!第九章 C语言内存管理完全指南:四区模型、堆栈操作与malloc/free核心实践(2015年C语言培训班笔记重读)

10、回看经典!第十章 C语言结构体完全指南:内存对齐、动态管理与指针应用实战(2015年C语言培训班笔记重读)

11、回看经典!第十一章 C语言文件操作与数据处理实战:从结构体到文本加密、排序全解析(2015年C语言培训班笔记重读)

12、回看经典!第十二章 C语言二进制文件操作实战:fread/fwrite读写、fseek随机访问与加密排序案例(2015年C语言培训班笔记重读)

13、回看经典!第十三章 C语言数据结构与算法基础:文件操作、排序查找实现及链表简介(2015年C语言培训班笔记重读)

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐