回看经典!第七章 全面解析C语言指针:从核心概念到高效应用(2015年C语言培训班笔记重读)
本文摘要: 本章系统讲解了C语言指针的核心概念与应用。首先区分了main函数中return与exit的区别,以及子函数中二者的不同作用。详细阐述了指针的本质是存储内存地址的变量,介绍了指针定义、初始化及操作(*p表示数据,p表示地址)。通过案例演示了指针与数组的关系,包括数组元素访问、地址偏移及内存占用情况。重点讲解了指针运算的特殊性(基于数据类型字节数移动)、指针兼容性问题、空指针与野指针的区别
目录
计算机科学与技术 & 计算机网络技术:双专业课程体系完全导航指南
第七章 指针
第1课:函数复习
main函数与exit函数
在main函数中调用return和调用exit的效果是一样的。
在子函数中调用exit代表整个程序退出了,但调用return只是这个子函数退出,程序主体还在运行。
exit(0);
return 0;
第2课:指针的含义与定义
指针的概念
指针也是一个变量。
指针存放的内容是一个地址,该地址指向一块内存空间。
计算机的内存最小单位是Byte(每个Byte有8个位(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表示指针。p是pointer的意思。&是取变量地址的意思
指针变量的定义
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。
2、p代表指针地址。
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]:
1、s 表示的就是地址,s 等同于 &s
2、s[1]、s[2]、s[3]...表示数据
3、& s[0]、& s[2]、& s[1]...表示址
4、s 等于&s[0]
5、char数组成员间相隔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类型的变量只能保存 -128~127之间的数。可见char是个有符号数
【补充:上面说法不对,之所以只能保存-128~127之间的数,是由于定义的问题,如果定义为unsigned char就可以保存0~255之间的数,(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为-128,1111111为-127(原码),01111111为127,00000000为0

案例:通过指针偏移修改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定义在printf与sscanf后则出错
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中,变量定义在printf、sscanf后时,程序出错,如下:
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;
}
效果如下:

注释:为什么j从1开始?(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,但编号却是0~9
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语言培训班笔记重读)
更多推荐


所有评论(0)