目录
1概念
内存中每个字节的空间都有一个编号,这个编号叫做地址,也叫做指针,也就是说,指针变量就是地址编号;保存指针的变量称之为指针变量。
平时使用中:地址编号:地址 保存地址的变量:指针
2指针的相关操作
& 获取变量的地址
* 在定义变量时加上*,只起到标识作用,标识定义的变量是一个指针变量
其他场景遇到*表示操作地址中的内容
3指针和变量的关系
p中保存了a的地址,也就是0x20
通过p可以操作0x20地址里的内容
*p = 100; //表示将0x20地址开始的4个字节(因为p是int类型的指针)的空间置成100
//等价于 a = 100;
//内存中每个字节的空间都有自己的编号,这个编号就是地址
//当程序运行到定义变量时,会根据变量的类型,给变量分配合适大小的空间用来保存数据。
int a = 10;
//可以通过 &变量名 获取变量的地址,变量的地址一般用指针变量保存
//存储类型 &变量名 *指针变量名;
// %p 输出地址的
int *p = &a; //用a的地址初始化指针变量p
printf("&a = &p p = %p\n",&a,p);
int *p2 = &100; //错误,常量没有地址可言
//可以通过p修改a的值
*p = 20; //等价于a = 20;
printf("a = %d *p = %d\n",a,*P);
//一般不使用普通变量保存地址,因为无法进行 *p变量名 的操作
//long int temp = &a; //警告
//printf("temp = %#lx\n",temp);
//*temp = 30; //错误
#if 0
a = 0x12345678;
cahr *p3 = &a;
printf("&a = &p p3 = %p\n",&a,p3);
//指针能访问几个字节,取决于定义时给定的类型,与指针保存地址的变量的类型无关
printf("&a = %#x *p3 = %#x\n",a,*p3);//a = 0x12345678 *p3 = 0x78
#endif
//指针变量自身也需要占用空间来存储地址数据
//64位系统:指针变量占8个字节 32位系统:指针变量占4个字节
cahr *t1; //short、int、float、long
printf("sizeof(t1) = %ld\n",sizeof(t1));
//指针变量本质也是一个变量,也需要分配空间保存数据
printf("&t1 = %p\n",&t1);
4指针变量的运算
指针变量中保存的地址的相关运算
地址只能进行如下运算:+ - ++ -- > < >= <= == != =
注意:相同类型的指针之间做运算才有意义。
int s[5] = {10,20,30,40,50};
int *p1 = &s[0];
int *p2 = &s[3];
//两个指针做差得到的是相差的数据类型的个数
//数据类型指的是:定义指针时的类型
printf("p2-p1 = %ld\n",p2-p1); //3
char *p3 = (char *)&s[0];
char *p4 = (char *)&s[3];
printf("p4-p3 = %ld\n",p4-p3); //12
int *p5 = &s[0];
printf("p5 = %p *p5 = %d\n",p5,*p5);
int *p6 = p5+1; //指针+1 是加了一个数据类型(定义指针时的类型),并不是地址数据+1
printf("p6 = %p *p6 = %d\n",p6,*p6);
//指针变量可以被赋值(改变指向)
int a = 10;
int *q = &a;
int b = 20;
q = &b;//改变的是值,如果是*q = &b;则改变的是地址
5指针和一维数组
int s[6] = {10,20,30,40,50,60};
printf("s[0] = %d\n",s[0]);
printf("s[3] = %d\n",s[3]);
//数组名是一个地址常量,保存的时数组的首地址
//通过 数组名[下标] 的方式访问数组成员
//本质上以数组名存的地址为基准,偏移几个数据元素,然后取值的操作
printf("s = %p *s = %d\n",s,*s);
printf("s+3 = %p *(s+3) = %d\n",s+3,*(s+3);
int *p = s; //可以将数组名的地址赋值给指针变量
int *p2 =&s[0];
//int *p = &s;
//&数组名 数组名 &s[0] 这三个地址的值是一样的,但 &数组名 会有警告(不使用)
//当指针变量保存了数组的首地址后
s[i] == *(s+i) == *(p+i) == p[i]
//一维数组的遍历
int i = 0;
for(i=0;i<6;i++){
//printf("%d ",s[i]);
//printf("%d ",*(s+i));
//printf("%d ",p[i]);
printf("%d ",*(p+i));
}
p++; //p是指针变量,可以被赋值 p = p+1
//s++; //错误,s是地址常量,不可以被赋值
用指针实现strcpy功能
#include <stdio.h>
int main(int argc,const char*argv[]){
char dest[64] = "abcd";
cahr src[] = "www.hqyj.com";
cahr *p = dest;
cahr *q = src;
while(*q != '\0'){
*p++ = *q++;
}
*p = *q; //将最后的 \0 也复制过来
p = dest; //需要将p重新指向dest的首地址
printf("%s\n",p);
return 0;
}
6指针和二维数组
int s[3][4] = {{1,2,3,4},
{5,6,7,8},
{9,10,11,12}};
//二维数组的数组名也是一个地址常量,是一个行指针,每次 +1 偏移的是一行数据元素
printf("s = %p s+1 = %p\n",s,s+1); //相差16
//*s 对二维数组的数组名取*,相当于对行指针进行降维,将为列指针
printf("*s = %p *s+1 = %p\n",*s,*s+1); //相差16
//如果想取到二维数组中的数据,需要加两个*
//s[i] == *(s+i)
//s[i][j] == *(*(s+i)+j)
printf("%d\n",**s); //1
printf("%d\n",*(*s+1)); //2
printf("%d\n",*(*(s+1))); //5
printf("%d\n",*(*(s+1)+1)); //6
printf("%d\n",*(*(s+2)+3)); //12
//由于二维数组的数组名是一个行指针,每次+1是偏移一行,所以不能使用普通的指针去保存二维数组数组名的地址
int *p = s; //警告,一维数组可以,但二维数组不可以
//p+1是偏移一个int,s+1是偏移一行
//二维数组的遍历
int i = 0;
int j = 0
for(i=0;i<3;i++){
for(j=0;j<4;j++){
//printf("%d ",s[i][j]);
printf("%d ",*(*(s+i)+j));
}
printf("\n");
}
7数组指针
本质是一个指针,指向一个数组(二维数组) 也称之为 行指针
格式:数据类型 (*数组指针名)[列数];
int s[3][4] = {{1,2,3,4},
{5,6,7,8},
{9,10,11,12}};
//多用于二维数组的传参
int (*p)[4] = s;
printf("p+1 = %p s+1 = %p\n",p+1,s+1); //地址是一样的
//s[i][j] == *(*(s+i)+j) == p[i][j] ==*(*(p+i)+j)
int i = 0;
int j = 0
for(i=0;i<3;i++){
for(j=0;j<4;j++){
//printf("%d ",s[i][j]);
//printf("%d ",*(*(s+i)+j));
//printf("%d ",p[i][j]);
}
printf("\n");
}
p++; //p是变量,s是常量
//s++; //报错
//关于一维数组名取地址
int s[5]= {1,2,3,4,5};
//&s:相当于给s这个列支指针升维成行指针,编译器会理解成你的 p+1 偏移一整行,但p值偏移一个int 所以int *p = &s;没人用
//可以使用数组指针消除警告,但指针就不能偏移了,偏移越界
int (*p)[5] = &s;
printf("%d\n",*(p+1));//越界
8指针数组
本质是一个数组,数组中的每一个成员都是一个指针
格式:数据类型 *数组指针名[数组长度];
//可以用二维数组保存多个字符串,可能会出现空间上的严重浪费
char s[4][64] = {"liu","wang","wei","asdasdasfa.asdsafasfa.aa"};
int i = 0;
for(i = 0;i<4;i++){
printf("%s\n",s[i]);
}
printf("sizeof(s) = %ld\n",sizeof(s)); //4*64*1(数据类型) = 256
//定义指针数组,指向字符串
char *p[4]; //定义一个长度为4的数组,数组的每一个元素都是一个char*
//p是数组名
printf("sizeof(p) = %ld\n",sizeof(p)); //4*8 = 32
char name1[] = "liu";
char name2[] = "wang";
char name3[] = "wei";
char name4[] = "asdasdasfa.asdsafasfa.aa";
p[0] = name1;
p[1] = name2;
p[2] = name3;
p[3] = name4;
for(i = 0;i<4;i++){
printf("%s\n",p[i]);
}
//初始化方式
char *p2[4] = {"hello","world","beijing","nihao"};
for(i = 0;i<4;i++){
printf("%s\n",p2[i]);
}
printf("%c\n",p2[1][1]); //取world的o
9指针和字符串
//定义一个字符数组,保存字符串
char str[] = "hello";
printf("%s\n",str);
//字符数组保存在字符串的栈区上,可以修改
str[1] = 'E';
//操作系统不会将相同的地址空间同时分配给两个变量
char str2[] = "hello";
//用一个char *类型的指针指向一个字符串
//指针指向的字符串是一个 字符串常量 在常量区不可以修改
cahr *p = “world”;
printf("%s\n",p);
p[1] = '0'; //编译不报错,执行段错误
//不管有多少个指针,只要指向同一个字符串常量,那保存的地址就是相同的
cahr *p2 = “world”;
//内存
//栈区(int a; int*p系统自动分配,自动回收)和堆区(程序员自己分配,自己释放)都可以修改,但是静态区(100,abc常量)不可以修改
10二级指针
//二级指针多用于一级指针传参----数据结构经常用
int a = 10;
int *p &a;
int **q = &p;//定义二级指针保存一级指针的地址
//a == *p == **q ==10 取值
//&a == p == *q 取地址
printf("a == %d *p = %d **q = %d\n",a,*p,**q);
printf("&a = %p p = %p *q = %p\n",&a,p,*q);
//可以用一级指针保存一级指针的地址,只能保存,不能取**操作
int *p1 = &p;
printf("p1 = %p q = %p\n",p1,q); //地址是相同的
printf("%d\n",**p1); //错误
11const关键字
const是C语言的一个关键字,用来修饰变量,表示只读
int a = 10;a = 20;
const int b; b = 20; //错误,b的值是只读
//const修饰的a是只读的,对于只读变量 潜规则要求必须初始化,否则就是随机值且不能修改
const int a = 10;
//a = 20; //错误,不可以修改
//const修饰指针
int m = 10;
int n = 20;
//const 修饰的是*p,表示不能通过 指针p1 修改m的值
const int *p1 = &m;
*p1 = 50; //错误,不能通过*p修改
m = 50; //但是可以通过m修改,因为m没有const修饰
//和上面一样
int const *p2 = &m;
//const修饰的是p,表示p不能改 指针不能改变指向
int *const p3 = &m;
//*P3 = 100; //允许
//p3 = &n; //错误
//指针的指向不能修改,也不能通过指针修改指向内容
const int *const p4 = &m;