指针的优点:
- 使程序更简洁、紧凑、高效
- 有效的表达更复杂的数据结构
- 动态分配内存
- 得到多于一个数的函数返回值
1. 指针的基本用法
1.1 指针的概念
-
内存地址:内存中每个字节单位都有一个编号(一般用十六进制表示)
-
指针:指针就是内存地址
-
指针变量:用于存放地址的变量就叫做指针变量
指针变量画图展示:
1.2 格式
存储类型 数据类型 *指针变量名;
int *p; //定义了一个指针变量p,指向的数据是int类型的。
int a = 5;
int *p = &a;
char c='v';
char *q=&c;
printf("%p %p\n", p, &a);
printf("%p %p\n",q,&c);
printf("%d %d\n",a,*p);
printf("%c %c\n",c,*q);
访问指针所指向空间的内容用取内容运算符*
那么p变量存放的就是a的地址,q变量存放的是c的地址。
符号*可以访问地址里面的内容。
指针与所指变量之间的关系如下图:
int i=3;
int *i_potinte=&i;
2.3 指针操作符
-
&:取地址符:取变量的地址
-
*:取内容符:取地址里面存放的内容
*&a=a;//*和&是互逆运算
&*a//错误(因为运算符优先级)
2.4 指针变量初始化和赋值
指针变量使用前不仅要定义,还要初始化。未初始化的指针变量不能随便使用,不然可能会产生野指针。
(1)将普通变量的地址赋值给指针变量
#include
int main(int argc, char const *argv[])
{
int a=10;
int *p = &a;//定义的同时赋值
int *q=NULL;//可以初始化为空指针
int *p2;
p2=p;//先定义后赋值
printf("%d %d %d\n",a,*p,*p2);//打印a的值
printf("%p %p %p\n",&a,p,p2); //打印a的地址
*p=20; //同过指向a的指针改变a的值
printf("%d %d %d\n",a,*p,*p2);//打印a的值
printf("%p %p %p\n",&a,p,p2); //打印a的地址
return 0;
}
(2)将数组的首地址赋值给指针变量
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
(3)将指针变量里面保存的地址赋值给另一个指针变量
int a=10;
int *p=&a;
int *q=p;
printf("%d %d %d\n",a,*p,*q);
*q=20;//通过指针改变变量a的值
printf("%d %d %d\n",a,*p,*q);
p和q都指向了变量a
2. 指针运算
2.1 算术运算- +
对指针加减操作其实就是让指针向前向后移动
char str[32]="hello";
char *p=str;
printf("%c\n",*p);
p++;
printf("%c\n",*p);
p--;
printf("%c\n",*p);
int *p;p++;//移动4字节
double *q;q++;//移动8字节
p+n:访问高地址方向的第n个数据的地址,指针的指向不发生变化。
p-n:访问低地址方向的第n个数据的地址,指针的指向不发生变化。
偏移了多少字节=n*sizeof(数据类型)
2.2 关系运算 > >= < <= == !=
指针之间的关系运算比较的是它指向地址的高低
指向高地址的指针是大于指向低地址的指针
char s[10]="hello";
char *p1=s;
char *p2=&s[2];
if(p2>p1)
printf("p2>p1");
else
printf("p2);
练习:以下程序打印出来什么
char s[32]="hello";
char *p=s;
p++;c
char y=(*--p)++;
printf("%c",y);//h
练习:以下程序打印什么结果
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
练习:下面程序段的运行结果是
char a[ ]=”language” , *p ;
p=a ;
while (*p!=’u’) { printf(“%c”,*p-32); p++ ; }
A)LANGUAGE B)language C)LANG D)langUAGE
C
3. 指针的大小和段错误
3.1 指针的大小
计算指针大小用:sizeof(指针变量名)
int a=5;
short b=6;
char c='a';
int *p1=&a;
short *p2=&b;
char *p3=&c;
printf("%d %d %d\n",sizeof(p1),sizeof(p2),sizeof(p3));//4 4 4 4
总结:
-
32位操作系统指针大小是4字节。64位操作系统,指针大小是8字节。
-
内存地址是固定的,但是变量的地址不是固定的。
-
指针类型根据指针指向的空间的数据类型来匹配
练习:
利用指针判断输入的字符串是否为回文
#include
#include
int main(int argc, char const *argv[])
{
char a[32]={};
char *p1,*p2;
int flag=1;
gets(a);
p1=a;
p2=a+strlen(a)-1;
while(p2>p1)
{
if(*p1!=*p2)
flag=0;
p1++;
p2--;
}
if(flag==0)
printf("no!\n");
else
printf("yes!\n");
return 0;
}
练习:编写一个程序实现功能:将字符串“Computer Science”赋值给一个字符数组,然后从第一个字母开始间隔的输出该字符串,用指针完成。结果:Cmue cec
#include
#include
int main(int argc, char const *argv[])
{
char string[32] = "Computer Science";
char *p = string;
while (*p != '\0')
{
printf("%c", *p);
p += 2;
}
printf("\n");
return 0;
}
3.2 使用指针时报段错误
段错误: Segmentation fault (core dumped)
(1)野指针,没有规定指向的指针会在内存中乱指。
野指针产生的原因,主要有两种:
【1】指针变量没有初始化
【2】指针被free之后,没有置NULL,会让人认为时合法指针。
这两种情况都是没有给指针规定指向。
例如:int *p;
scanf(“%d”, p);
printf(“%d\n”,*p);
修改:
int a=10;
int *p=&a;
scanf("%d",p);
printf("%d\n",*p);
(2)内存泄露,对非法空间进行赋值。
练习1.将字符串转换成整型数字输出。用指针实现
#include
#include
int main(int argc, char const *argv[])
{
char a[32]="123";
char *p=a;
int num=0;
while(*p != '\0')
{
num = num*10+(*p-48);
p++;
}
printf("num=%d\n",num);
return 0;
}
4. 指针修饰
4.1 const 常量化
(1)修饰普通变量
const int a=10;
int const b=10;
此时a和b只读,不可以修改。但是可以通过指针修改。
const int a=10;
int const b=20;
int *p=&a,*q=&b;
printf("%d %d\n",a,b);//10 20
*p=1;
*q=2;
//a=1;//error: assignment of read-only variable ‘a’
//b=2;//error: assignment of read-only variable ‘b’
printf("%d %d\n",a,b);//1 2
(2) 修饰指针指向的内容
也就是修饰*p,此时指针指向的内容不能改变,但是指向可以改变。
const int *p;
int const *p;
#include <stdio.h>
int main(int argc, char const *argv[])
{
int a=10;
int b=20;
int const *p=&a;
const int *q=&b;
printf("%d %d\n",a,b);//10 20
//*p=1;//*p只读不可以被更改
//*q=2;//*q只读不可以被更改
p=q;
printf("%d %d\n",*p,*q);//20 20
return 0;
}
(3) 修饰指针指向
int *const p;
此时const修饰p,指针指向不能被更改,但是指向的内容可以被更改。
#include <stdio.h>
int main(int argc, char const *argv[])
{
int a=10;
int *const p=&a;
printf("%d\n",*p);
//p=NULL;//error: assignment of read-only variable ‘p’
*p=20;
printf("%d\n",*p);
return 0;
}
4.2 void
(1) 不允许修饰普通变量:void a;//错误
(2) 可以修饰指针:void *p;
此时p是一个任意类型的指针
使用场景:函数参数或函数返回值。
注意:通过void类型修饰的指针进行取内容时,需要进行强制转换。
转换方式:(int *)p 为右值
int a=100;
void *p=&a;
int *q=(int *)p;
printf("%d %d\n",*(int *)p,*q);
👉现用现转,或者转换之后赋值给另外一个变量然后应用另外一个变量。
4.3 大小端
在计算机进行超过1字节数据进行存储时,会出现存储数据顺序不同的情况即大小端存储
-
大端:数据的低位存储在高地址位,数据的高位存储在低地址位,大端字节序称为MSB
-
小端:据的低位存储在低地址位,数据的高位存储在高地址位,小端字节序称为LSB
举例:存储数据:0x12345678, 假设起始地址是0x4000
地址:0x4000 0x4001 0x4002 0x4003
小端:0x78 0x56 0x34 0x12
大端:0x12 0x34 0x56 0x78
#include
#include
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");
return 0;
}
5. 二级指针
-
一级指针:存放变量的地址
-
二级指针:存放一级指针的地址
5.1 格式
存储类型 数据类型 **指针变量名;
int a = 10;
int *p = &a;
int **q = &p;
此时p指向了a,q指向了p也就是q里保存了p的地址。访问a的值,a的地址和p的地址:
a的值 | a的地址 | p的地址 |
---|---|---|
a | &a | &p |
*p | p | q |
**q | *q |
验证:
#include
#include
int main(int argc, char const *argv[])
{
int a=10;
int *p=&a;
int **q=&p;
printf("%d %d %d\n",a,*p,**q);
printf("%p %p %p\n",&a,p,*q);
printf("%p %p\n",&p,q);
return 0;
}
结果:
10 10 10
0xbf9839b0 0xbf9839b0 0xbf9839b0
0xbf9839b4 0xbf9839b4
6.指针和一维数组
6.1 用法
int a[5]={1,2,3,4,5}; //a是数组名,也是数组首地址。
int *p=a;
(1)直接访问:
(2)通过地址间接访问:
访问数组元素a[i]的地址:
-
直接访问:&a[i] 或 a+i
-
间接访问:&p[i] 或 p+i
访问数组元素a[i]的内容:
-
直接访问:a[i] 或 *(a+i)
-
间接访问:p[i] 或 *(p+i)
#include <stdio.h>
int main(int argc, char const *argv[])
{
int a[3]={1,2,3};
int *p=a;
printf("%p %p %p %p\n",a,p,&a[0],&p[0]);
printf("%p %p %p %p\n",a+1,p+1,&a[1],&p[1]);
printf("%d %d %d %d\n",a[0],p[0],*a,*p);
printf("%d %d %d %d\n",a[1],p[1],*(a+1),*(p+1));
return 0;
}
打印结果:
0xbfb27e90 0xbfb27e90 0xbfb27e90 0xbfb27e90
0xbfb27e94 0xbfb27e94 0xbfb27e94 0xbfb27e94
1 1 1 1
2 2 2 2
练习:用指针实现字符串“hello”的倒序打印。
#include <stdio.h>
#include <string.h>
int main(int argc, char const *argv[])
{
char str[32]="";
char *p1=str,*p2=NULL;
int temp=0;
gets(str);
p2=p1+strlen(str)-1;
while (p2>p1)
{
temp=*p1;
*p1=*p2;
*p2=temp;
p1++;
p2--;
}
printf("%s\n",str);
return 0;
}
练习:打印杨辉三角形前十行
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
1 5 10 10 5 1
1 6 15 20 15 6 1
#include <stdio.h>
int main()
{
int a[10][10], i, j;
/*负责对角线和每行第一列*/
for (i = 0; i < 10; i++)
{
a[i][i] = 1;
a[i][0] = 1;
}
/*负责第三行起,每个数等于左上方和正上方的数相加*/
for (i = 2; i < 10; i++)
{
for (j = 1; j <= i - 1; j++)
a[i][j] = a[i - 1][j - 1] + a[i - 1][j];
}
for (i = 0; i < 10; i++) //打印结果
{
for (j = 0; j <= i; j++)
printf("%4d ", a[i][j]);
printf("\n");
}
return 0;
}
6.2 运算方法
(1)++和*都是单目运算符
(2)单目运算符从右向左运算
(3)++在前是先移动再取值,++在后是先取值再移动。
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));//同上
//printf("%d\n",*++p);//2
printf("%d\n",*(++p));//同上
6.3 指针和二维数组
6.3.1 数组名表示
例如:
int a[2][3]={1,2,3,4,5,6};
a是数组名,表示第一行的首地址。
a+1表示第二行的首地址,以此类推。
在a前面加*,表示将行地址降级称为列地址。
*a;//第一行第一列的地址
*a+1;//第一行第二列的地址
*(a+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)
代码验证:
#include <stdio.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));
return 0;
}
打印结果:
1 1
2 2
3 3
4 4
5 5
6 6
7.数组指针
7.1 概念
本质还是指针,指向的是数组(又称为行指针)
7.2 定义格式
存储类型 数据类型(* 指针变量名)[列数];
例如:
int a[2][3]={1,2,3,4,5,6};
int (*p)[3]=a;
p可以代替进行元素访问,但本质不同,p是指针变量,a是地址常量。
把p进行运算的时候,例题中情况要3个单位3个单位进行运算。
访问数组元素地址(a[i][j]的地址):
*(p+i)+j
p[i]+j
访问数组元素值:
*(*(p+i)+j)
*(p[i]+j)
例如:用数组指针遍历二维数组。
#include <stdio.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;
}
练习:
有一个班,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>
int main(int argc, char const *argv[])
{
int a[3][4]={65,55,23,57,67,80,90,42,75,92};
int (*p)[4]=a;
int sum=0,ave=0,n=0;
for(int i=0;i<3;i++)
{
for(int j=0;j<4;j++)
sum+=*(p[i]+j);
}
ave=sum/12;
printf("sum=%d ave=%d\n",sum,ave);
scanf("%d",&n);
if(n<=3&&n>0)
{
for(int k=0;k<4;k++)
printf("%d ",*(*(p+n-1)+k));
printf("\n");
}
return 0;
}
7.3 大小
sizeof(p)=4
因为本质还是指针,所以大小都是四字节。
8. 指针数组
8.1 定义
所谓指针数组是指若干个具有相同存储类型和数据类型的指针变量构成的集合。其本质是数组,里面存放的是指针。
8.2格式
存储类型 数据类型 *数组名[元素个数];
指针数组名表示的是该指针数组的首地址。
例如:
int *arr[2];
8.3 应用
(1) 用于存放普通变量的地址
例如:
int a=10,b=20,c=30;
int *p[3]={&a,&b,&c};
访问b的值:
*p[1]
**(p+1)
访问b的地址:
p[1]
*(p+1)
int a=10,b=20,c=30;
int *p[3]={&a,&b,&c};
printf("%p %p %p\n",p[0],p[1],p[2]);
printf("%p %p %p\n",*p,*(p+1),*(p+2));
printf("%d %d %d\n",*p[0],*p[1],*p[2]);
printf("%d %d %d\n",**p,**(p+1),**(p+2));
(2) 用于存放二维数组的每行第一个元素的地址(列地址)
例如:
int a[2][3]={1,2,3,4,5,6};
int *p[2]={a[0],a[1]};
访问a[1][2]的值:
*(p[1]+2)
*(*(p+1)+2)
访问a[1][2]的地址:
p[1]+2
*(p+1)+2
大小:sizeof(p); //8,因为包含两个指针,每个指针大小为4。
访问地址:
- p[i]+j
- *(p+i)+j
访问元素:
- *(*(p+i)+j)
- *(p[i]+j)
(3) 用于存放字符串
char *p[3]={“hello”,“world”,“hqyj”};
练习:遍历字符串中每一个元素
#include <stdio.h>
int main(int argc, char const *argv[])
{
char *p[3] = {"hello", "world", "hqyj"};
/*
for(int i=0;i<3;i++)
{
int j=0;
while(*(p[i]+j)!='\0')
{
//printf("%c",*(p[i]+j));
printf("%c",*(*(p+i)+j));
j++;
}
printf("\n");
}
*/
for (int i = 0; i < 3; i++)
// printf("%s\n",*(p+i));
printf("%s\n", p[i]);
return 0;
}
练习:用行指针实现杨辉三角
#include<stdio.h>
int main(int argc, char const *argv[])
{
int a[10][10]={};
int (*p)[10]=a;
for(int i=0;i<10;i++)
{
*p[i]=1;
*(*(p+i)+i)=1;
}
for(int i=2;i<10;i++)
{
for(int j=1;j<10;j++)
{
*(p[i]+j)=*(p[i-1]+j)+*(p[i-1]+j-1);
}
}
for(int i=0;i<10;i++)
{
for(int j=0;j<=i;j++)
printf("%d ",*(p[i]+j));
printf("\n");
}
return 0;
}
练习:
- 遍历char *p[3]={“hello”,“world”,“hqyj”};中的每个元素
- 用指针将整型组s[8]={1,2,3,4,5,6,7,8}中的值逆序存放并打印
#include <stdio.h>
int main()
{
int i;
int s[8]={1,2,3,4,5,6,7,8};
int *p=s,t=0;
int *q=s+sizeof(s)/sizeof(s[0])-1;
while (p<q)
{
t=*p;
*p=*q;
*q=t;
p++;
q--;
}
for(int i=0;i<8;i++)
printf("%d ",s[i]);
printf("\n");
return 0;
}
- 把一句话倒叙排放,比如"i love china"倒叙成“china love i”
(方法一):
#include<stdio.h>
#include<string.h>
int main(int argc, char const *argv[])
{
char a[32]="you love china";
char *p = a,*q=NULL,*k=NULL,t;
q = p+strlen(a)-1; //指向整个字符串的结尾
while(p
{
t = *p;
*p = *q;
*q = t;
p++;
q--;
} //全部倒置结束
printf("%s\n",a);
p = q = a; //重新指向字符串开头
while(*p != '\0') //遍历整个字符串
{
while(*p == ' ') //让p一直指向开头
p++;
q=p; //p和q指向单词开头
while(*q != ' '&& *q != '\0')
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>
#define MAX 30
int main(void)
{
char buf[32];
char str[MAX][15] = {0};
int i, j, t;
i = MAX - 1; //表示最后一行
t = 0;
gets(buf);
while (buf[t] != '\0')
{
j = 0;
while (buf[t] != '\0' && buf[t] != ' ')
{
str[i][j] = buf[t]; //二维数组的每一行记录字符串
j++;
t++;
}
if (buf[t] == ' ')
{
i--; //找上一行,把字符串塞到上一行里。
t++;
}
}
for (; i < MAX; i++) //依次打印每个小字符串
printf("%s ", str[i]);
printf("\n");
return 0;
}
(方法三):
#include <stdio.h>
#define MAXN 32
#define MAXM 32
int main(void)
{
char buf[32];
char str[MAXN][MAXM] = {0};
char(*p)[MAXM] = str + MAXM - 1;
int j;
char *q=buf;
gets(buf);
while (*q != '\0' )
{
j = 0;
while (*q != '\0' && *q != ' ' )
{
*(*p + j) = *q;
j++;
q++;
}
if (*q == ' ')
{
p--;
q++;
}
}
for (int i = 0; i < MAXN; i++)
if (*str[i])
printf("%s ", str[i]);
printf("\n");
return 0;
}
(4) 命令行参数
int main(int argc, char const *argv[])
{
printf("%s\n",argv[0]);
}
-
argv:是一个指针数组,里面存放了命令行传递的字符串。
-
argc:表示的是argv指针数组里面字符串的个数,也就是命令行传递字符串的个数。
例子:打印命令行除了开头名字的每一个参数字符串
#include<stdio.h>
int main(int argc, char const *argv[])
{
for (int i = 1; i < argc; i++)
printf("%s\n",argv[i]);
return 0;
}
练习:将命令行输入的3个字符串转换成整型数字输出。要求:字符串为0-9组成,输出数据为一个整形数
#include <stdio.h>
#include <string.h>
int main(int argc, char const *argv[])
{
int len=0,arr[4]={}; //用一个数组接收没一个转换后的数字
for(int i=1;i<4;i++)
{
len=strlen(argv[i]); //计算每个字符串长度
arr[i-1]=*argv[i]-48; //转换每个字符串的第一个数组
for ( int j=1; j < len; j++)
{
arr[i-1]=arr[i-1]*10+(*(argv[i]+j)-48); //用于接收后面的转换
}
}
for(int k=0;k<3;k++)
printf("%d\n",arr[k]);
return 0;
}