指针的优点:
- 使程序更简洁、紧凑、高效
- 有效的表达更复杂的数据结构
- 动态分配内存
- 得到多于一个数的函数返回值
指针的基本用法
指针的概念
地址:内存中每个字节单位的编号(一般用十六进制表示)。
指针:指针就是地址。
指针变量:用于存放地址的变量就叫指针变量。
普通变量:
指针变量:
定义格式
存储类型 数据类型 *指针变量名;
例如:
int *p;//定义了一个整数型指针变量p
例如:
int a=5; int *p=&a; char b='v'; char *q=&b; printf("%p %p\n",p,&a); printf("%p %p\n",q,&b); printf("%d %d\n",*p,a); printf("%d %d\n",*q,b);
访问指针所指的空间里的内容时,需要用取内容运算符*来获取:
此时,变量p存放的就是a的地址,变量q存放的就是b的地址;符号*可以访问地址指向的空间里面的内容。
指针与变量之间的关系如下:
int i=3;
int i_pointer=&i;
指针操作符
取地址运算符(&):取变量地址
取内容运算符(* ):取地址指向的空间里的内容
*&a=a;//*和&是相互逆运算
&*a; //错误,运算符优先级
初始化和赋值
指针变量在使用前不仅要定义还得初始化,未初始化指针变量不能随便使用,不然会产生野指针。
将普通变量的地址赋值给指针变量
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc,char const *argv[])
{
int a=10;
int *p=&a;//定义指针变量的同时赋值
int *p1=NULL;//可以给指针赋值为空指针
int *q;
q=p;//先定义后赋值
printf("%d %d %d\n",a,*p,*q);//打印a的值
printf("%p %p %p\n",&a,p,q);//打印a的地址
*q=20;//通过*q改变指针指向内存里的内容
printf("%d %d %d\n",a,*p,*q);//打印a的值:20 20 20
PRINTF("%p %p %p\n",&a,p,q);//打印a的地址
return 0;
}
将数组的首地址赋值给指针变量
char s[10]="hello";
char *p=s;
int arr[5]={1,2,3,4,5};
int *q=arr;
printf("%c\n",*p);//h
printf("%d\n",*q);//1
将指针变量中保存的地址赋值给另一个指针变量
float a=1.5;
float *p=&a;
float *q=p;
printf("%f %f %f\n",a,*p,*q);
指针运算
算数运算 + -
对指针的加减操作实际上是让指针向前或者向后移动。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc,char const *argv[])
{
char s[32]="hello";
char *p=s;
int arr[5]={1,2,3,4,5};
int *q=arr;
printf("%c\n",*p);//h
p=p+2;
printf("%c\n",*p);//l
p--;
printf("%c\n",*p);//e
printf("%d\n",*q);//1
printf("%p\n",q);
q++;
printf("%d\n",*q);//2
printf("%p\n",q);//指针移动到下一个单位,数值上多了字节
return 0;
}
p+n:访问高地址方向的第n个数据的地址,指针指向不变。
p-n: 访问低地址方向的第n个数据的地址,指针指向不变。
关系运算 > >= < <= == !=
指针之间的关系运算比较的是地址的高低,高地址的指针大于低地址的指针。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int man(int argc,char const *argv[])
{
char s[32]="hello";
char *p1=&s[1];
char *p2=&s[4];
if(p2>p1)
printf("p2>p1\n");
else
printf("p1>p2\n");
return 0;
}
注意:
指向不同类型的数组指针关系运算没有意义, 指向不同区域的指针关系运算也没有意义;在同一个数组内进行比较。
练习:
y打印出来的是什么
char s[32]="hello"; char *p=s; char y=(*--p)++; printf("%c\n",y);//未知字符(随机字符)
以下程序都会打印什么结果
int x[]={10,20,30}; int *px=x; printf("%d,",++*px); //11 printf("%d\n",*px); //11 px=x; printf("%d,",(*px)++); //11 printf("%d\n",*px); //12 px=x+1; printf("%d,",*px++); //20 printf("%d\n",*px); //30 px=x+1; printf("%d,",*++px); //30 printf("%d\n",*px); //30
下面这段程序的运行结果是(C)
char a[]="language",*p; p=a; while(*p!='u') { printf("%c",*p-32); p++; }
A. LANGUAGE B. language C. LANG D. langUAGE
利用指针判断输入的字符串是否为回文
#include <stdio.h> #include <stdlib.h> #include <string.h> int main(int argc,char const *argv[]) { char s[32]; char *p1,*p2; int flag=1; gets(s); p1=s; p2=s+strlen(s)-1; //p2=&s[strlen(s)-1]; while(p2>p1) { if(*p1!=*p2) flag=0; p1++; p2--; } if(flag==0) printf("不是回文字符串\n"); else printf("是回文字符串\n"); return 0; }
打印杨辉三角形前十行
解析:杨辉三角如下
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
……
#include <stdio.h> #include <stdlib.h> int main(int argc,char const *argv[]) { int a[10][10]={},i,j; /*负责对角线和每一行的第一个元素*/ for(i=0;i<10;i++) { a[i][0]=1; a[i][i]=1; } for(i=2;i<10;i++) { for(j=1;j<i;j++) { a[i][j]=a[i-1][j]+a[i-1][j-1]; } } for(i=0;i<10;i++) { for(j=0;j<=i;j++) printf("%4d ",a[i][j]); printf("\n"); } return 0; }
程序实现的功能:将字符串“Computer Science”赋值给一个字符数组,然后从第一个字母开始间隔的输出该字符串,用指针完成。
解析:输出结果 为Cmue cec
#include <stdio.h> #include <stdlib.h> int main(int argc,char const *argv[]) { char s[32]="Computer Science"; char *p=s; while(*p!='\0') { printf("%c",*p); p+=2; } printf("\n"); return 0; }
指针的大小和段错误
指针的大小
计算指针大小:sizeof(指针变量名)
int a=5;
int *p=&a;
char c='a';
char *q=&c;
double b=1.5;
double *p2=&b;
printf("%d\n",sizeof(p)); //4
printf("%d\n",sizeof(q)); //4
printf("%d\n",sizeof(p2)); //4
总结:
- 32位操作系统中, 指针的大小都是4字节;在64位操作系统中,指针的大小都是8字节。
- 内存地址是固定的,但是变量地址不是固定的(栈区随机分配的)。
- 指针类型根据指针指向的空间数据类型而决定的。
使用指针时容易报段错误
指针虽然灵活好用,但是使用时应该注意,因为指针使用不当容易报段错误。
段错误:Segmentation fault(core dumped)
野指针(没有规定指向的指针,会在内存中乱指)
产生野指针的原因主要有两种:
1. 指针变量没有初始化就使用。
int *p;
printf("%d",*p);
2. 指针被free之后没有设置为NULL,会让人以为是合法指针从而开始使用。
内存泄漏(对非法空间进行赋值)
练习:
将字符串转换成整型数字输出,用指针实现。
要求:字符串为0-9组成,输出数据为一个整数
#include <stdio.h> #include <stdlib.h> int main(int argc,char const *argv[]) { char a[32]="124"; char *p=a; int num=0; while(*p!='\0') { num=num*10+(*p-48); p++; } printf("num=%d\n",num); return 0; }
指针修饰
const常量化
修饰普通变量
const int a
int const a
此时的a是只读状态,不可以修改。
const int a=10;
//int const a;
a=20; //错误,变量的值不可以修改
printf("%d\n",a);
报错: error: assignment of read-only variable ‘a’ a=20;
但是可以通过指针修改。
const int a=10;
int *p=&a;
*p=20;
printf("%d\n",a);
修饰指针所指内容
修饰*p,指针指向内容不能更改,但是可以更改指针的指向。
const int *p
int const *p
int a=10;
const int *p=&a;
//int const *p=&a;
*p=20;//错误,指针所指内容不可修改
printf("%d\n",a);
错误:
error: assignment of read-only location ‘*p’
*p=20;
可以改变指针的指向:
int a=10,b=20;
const int *p=&a;
printf("%d\n",*p);
p=&b;
printf("%d\n",*p);
修饰指针指向
int *const p
此时,修饰的是p,也就是修饰指针的指向,指针指向不能修改,但是所指内容可以修改。
int a=10,b=20;
int *const p=&a;
printf("%d\n",*p);
p=&b;//错误,指针指向不可以修改
printf("%d\n",*p);
错误:error: assignment of read-only variable ‘p’
p=&b;
所指内容可以改变:
int a=10,b=20;
int * const p=&a;
printf("%d\n",*p);
*p=b;
printf("%d\n",*p);
void
- 不允许修饰普通变量:void a;//错误
- 可以修饰指针变量:void *p;
用void修饰指针变量,则这个指针变量是任意类型的指针。
注意:
通过void类型指针进行取内容时,需要对指针进行强制转换。
转换格式:强转(int *)p为右值。
例如:
int a=100;
void *p=&a;
int *q=(int *)p;
printf("%d %d\n",*q,*(int *)p);
强制转换要随用随转。
大小端
在计算机进行超过一字节的数据的存储,会出现存储顺序不同的两种情况,也就是大端存储和小端存储。
大端存储:数据的低位存储在高地址处,数据的高位存储在低地址处,大端字节序列称为MSB。
小端存储:数据的低位存储在低地址处,数据的高位存储在高地址处,小端字节序列称为LSB。
例如:
存储数据0x12345678在0x4000处
地址 0x40000 0x4001 0x4002 0x4003
小端 0x78 0x56 0x34 0x12
大端 0x12 0x34 0x56 0x78
#include <stdio.h> #include <stdlib.h> int main(int argc,char const * argv[]) { int a=0x12345678; char *p=(char *)&a; if(*p==0x78) printf("LSB\n"); else if(*p==0x12) printf("MSB\n"); else printf("error\n"); return 0; }
二级指针
一级指针:存放变量的地址。
二级指针:存放一级指针的地址。
格式
存储类型 数据类型 **指针变量名;
p指向了a,q指向了p。
a的值 | a的地址 | p的地址 |
a | &a | &p |
*p | p | q |
**q | *q |
int a=10;
int *p=&a,**q=&p;
printf("%d %d %d\n",a,*p,**q);
printf("%p %p %p\n",&a,p,*q);
printf("%p %p\n",&p,q);
指针和数组
两种访问形式
直接访问:按变量的地址存取变量的值。(通过数组名访问)
间接访问:通过存放变量的地址的变量去访问。(通过指针访问)
指针和一维数组
用法
int a[5]={1,2,3,4,5};//a是数组名也是首地址
int *p=a;
直接访问:
通过指针间接访问:
访问数组元素a[i]的地址:
直接访问:&a[i] a+i
间接访问:&p[i] p+i
访问数组元素a[i]的值:
直接访问:a[i] *(a+i)
间接访问:p[i] *(p+i)
代码验证:
int a[3]={1,2,3}; int *p=a; printf("%p %p %p %p\n",a,&a[0],p,&p[0]); printf("%p %p %p %p\n",a+1,&a[1],p+1,&p[1]); printf("%d %d %d %d\n",*a,a[0],*p,p[0]); printf("%d %d %d %d\n",*(a+1),a[1],*(p+1),p[1]);
打印结果:
0xbfd100a0 0xbfd100a0 0xbfd100a0 0xbfd100a0
0xbfd100a4 0xbfd100a4 0xbfd100a4 0xbfd100a4
1 1 1 1
2 2 2 2
注意:a和p的数值一样,但是本质不同。
- a是地址常量,p是指针变量。
- a不能执行++操作,但是p可以。
运算方法
++和*都是单目运算符。
单目运算符都是从右向左运算的。
++在前先移动再取值,++在后先取值后移动 。
int a[5]={1,2,3,4,5};
int *p=a;
//printf("%d\n",*(p++));//1
//printf("%d\n",++*p);//2
//printf("%d\n",++(*p));//2
//printf("%d\n",*++p);//2
printf("%d\n",*(++p));2
指针和二维数组
二维数组数组名表示
例如:
int a[2][3]={1,2,3,4,5};
a是数组名,表示第一行的首地址,a+1表示第二行的首地址。
在a前面加*表示将行地址降级为列地址。
*a表示第一行第一列的地址,即&a[0][0]
*a+1表示第一行第二列的地址,即&a[0][1]
*(a+1)表示第二行第一列的地址,即&a[1][0]
*(a+1)+1表示第二行第二列的地址,即&a[1][1]
访问二维数组 元素的地址(a[i][j]的地址):
&a[i][j] *(a+i)+j a[i]+j
访问二维数组元素的内容(a[i][j]的内容):
a[i][j] *(*(a+i)+j) *(a[i]+j)
#include <stdio.h> #include <stdlib.h> #include <string.h> int main(int argc,char const argv[]) { int a[2][3]={1,2,3,4,5,6}; for(int i=0;i<2;i++) { for(int j=0;j<3;j++) printf("%d %d\n",*(*(a+i)+j),*(a[i]+j)); printf("\n"); } return 0; }
打印结果:
1 1
2 2
3 3
4 4
5 5
6 6
数组指针
定义
本质还是指针,指向的是数组。(又称为行指针)
格式
存储类型 数据类型 (*指针变量名)[列数];
例如:
int a[2][3]={1,2,3,4,5,6};
int (*p)[3]=a;
此时p可以代替a进行元素的访问,但是本质不同,a是地址常量,p是指针变量。
访问a[i][j]的地址:
p[i]+j *(p[i]+j)
访问a[i][j]的内容:
*(p[i]+j) *(*(p+i)+j)
#include <stdio.h> #include <stdlib.h> #include <string.h> int main(int argc,char const *argv[]) { int a[2][3]={1,2,3,4,5,6}; int (*p)[3]=a; printf("%d %d\n",*p[0],**p); printf("%d %d\n",*(p[0]+1),*(*p+1)); printf("%d %d\n",*(p[1]+1),*(*(p+1)+1)); return 0; }
打印结果:
1 1
2 2
5 5
练习:
有一个班,3个学生,各学4门课,计算总平均分以及输出第n个学生的成绩。
int a[3][4] = {65,55,23,57,52,67,64,80,90,42,75,92};
思路:用数组指针的方式把每个学生分别当成一个数组,int (*p)[4]表示,这样每列的元素就代表每门课的成绩。然后循环嵌套算出总成绩,总成绩除以12算出平均成绩。然后通过循环,找到第几个学生的成绩输出。
#include <stdio.h> #include <stdlib.h> #include <string.h> int main(int argc,char const *argv[]) { int a[3][4]={66,55,23,57,52,67,64,80,90,42,75,92}; int (*p)[4]=a,sum=0,ave,n; for(int i=0;i<3;i++) { for(int j=0;j<4;j++) sum+=*(p[i]+j); } ave=sum/12; scanf("%d",&n); if(n<=3) { for(int k=0;k<4;k++) printf("%d ",*(p[n-1]+k)); printf("\n"); } return 0; }
大小
sizeof(p)=4;
因为本质还是指针,所以大小都是4字节。
例如:
int a[3][4]={1,2,3,4,5,6};用数组指针遍历二维数组
#include <stdio.h> #include <stdlib.h> #include <string.h> int main(int argc,char const *argv[]) { int a[2][3]={1,2,3,4,5,6}; int(*p)[3] = a; for (int i = 0; i < 2; i++) { for (int j = 0; j < 3; j++) //printf("%d ", *(p[i] + j)); printf("%d ", *(*(p + i) + j)); printf("\n"); } return 0; }
已知字符数组a[10]和b[10]中元素的值递增有序,用指针实现将两个数组中元素按照递增顺序输出。如:char a[10]=”acdgjmno” ; char b[10]=”befhil”;->”abcdefghijlmno”
解析:利用指针比较两字符数组中的元素,若a中的小,打印a中字符,然后a的指针移动到下一个元素继续比较,反之打印b然后移动b指针到下一个元素继续比较。最后判断字符串是否结束,打印没有结束的字符串剩下部分。
#include <stdio.h> #inclide <stdlib.h> int main() { char a[10] = "acegijkm"; char b[10] = "bdfh"; char *p = a, *q = b; while (*p != '\0' && *q != '\0') { if (*p < *q) { printf("%c", *p); p++; } else { printf("%c", *q); q++; } } if (*p == '\0') printf("%s", q); else if (*q == '\0') printf("%s", p); return 0; }
给定一串字符"I love china",实现以单词为单位的逆序,如:"china love i"
解析:可以先全部倒过来:anihc evol i, 设置两个指针,分别指向头和尾,然后比较两指针进行移动,倒置字符串。然后在设置一个指针,遍历字符串用来寻找空格和末尾并记录,只要找到就把每个单词倒过来。
方法一:
#include <stdio.h> #include <string.h> int main() { char a[32] = "i love china"; char *p, *q, *k, t; p = a; q = p + strlen(a) - 1; while (q > p) { t = *p; *p = *q; *q = t; p++; q--; } p = q = a; while (*p != '\0') { while (*p == ' ') //让p一直指向开头 { p++; } q = p; //让q先指向开头 while (*q != ' ' && *q != '\0') //为了让q找空格 { q++; } k = q; //把空格的位置用k去记录 q--; //让q指向结尾 while (p < q) { t = *p; *p = *q; *q = t; p++; q--; } //每个小字符串倒置 p = k; //让p指向空格 } printf("%s\n", a); return 0;
方法二:
#include <stdio.h> #include <string.h> #define MAX 50 int main() { char a[32] = "i love china"; char str[MAX][100] = {}; int i = MAX - 1, j; int t = 0; while (a[t] != '\0') { j = 0; while (a[t] != ' ' && a[t] != '\0') { str[i][j] = a[t]; //二维数组倒每一行记录一个小字符串 t++; j++; } if (a[t] == ' ') //找上一行,为了把新的字符串放到上一行 { i--; t++; } } for (; i < MAX; i++) //依次打印每个字符串 { printf("%s ", str[i]); } return 0; }
方法三:
#include <stdio.h> #include <string.h> #define MAX 50 int main() { char a[32] = "i love china"; char str[MAX][100] = {}; int i = MAX - 1, j; int t = 0; while (a[t] != '\0') { j = 0; while (a[t] != ' ' && a[t] != '\0') { str[i][j] = a[t]; //二维数组倒每一行记录一个小字符串 t++; j++; } if (a[t] == ' ') //找上一行,为了把新的字符串放到上一行 { i--; t++; } } for (; i < MAX; i++) //依次打印每个字符串 { printf("%s ", str[i]); } return 0; }
指针数组
定义
所谓指针数组就是由若干个具有相同类型的指针变量组成的集合。本质是数组,数组里面存放的元素是指针。
格式
存储类型 数据类型 *数组名[元素个数];
指针数组名就表示该指针数组的首地址。
例如:
int *arr[3];
应用
用于存放普通变量的地址
例如:
int a=10,b=20,c=30; int *p[3]={&a,&b,&c}; printf("%d %d %d\n",**p,**(p+1),**(p+2)); printf("%d %d %d\n",*p[0],*p[1],*p[2]); printf("%p %p %p\n",*p,*(p+1),*(p+2)); printf("%p %p %p\n",p[0],p[1],p[2]); printf("%p %p %p\n",&a,&b,&c);
用于存放二维数组每一行第一个元素的地址(列地址)
int a[2][3]={1,2,3,4,5,6}; int *p[2]={a[0],a[1]}; printf("%d\n",*(p[1]+2)); printf("%d\n",*(*(p+1)+2));
sizeof(p)=8;
访问地址:p[i]+j *(p+i)+j
访问元素:*(*(p+i)+j) *(p[i]+j)
用于存放字符串
char *p[3]={"hello","new","world"};
printf("%s\n",p[1]);//new
printf("%c\n",*(p[2]+1));//o
printf("%c\n",*(*(p+2)+1));//o
命令行参数
例如:
执行./a.out hello world
int main(int argc, char const *argv[]) { printf("%d\n", argc); printf("%s\n", argv[0]);//./a.out printf("%s\n", argv[1]);//hello printf("%s\n", argv[2]);//world printf("%c\n", *(argv[1] + 1));//e return 0; }
argv:就是一个指针数组,里面存放的是命令行传递的字符串
argc:表示argv指针数组里面存储数据的个数,即命令行传递字符串的个数