指针
文章目录
1.指针
1.1 概念
内存中每个字节都有一个编号,这个编号叫做指针,也叫作地址。
专门用来存储这个编号的变量就叫做指针变量。
只不过,平时交流的时候,都是称呼:
指针:指针变量
地址:内存编号
1.2 指针相关的操作
& :取地址符,获取变量的地址
对于多字节的变量取地址,取到的是编号最小的那个,我们称之为首地址
*:在定义指针时,只起到一个标识作用,标识定义的是一个指针变量
其他场景下,标识操作指针保存的地址里面的内容
1.3 指针和变量的关系
1.4 指针的基本使用
#include <stdio.h>
int main(int argc, const char *argv[])
{
//当程序执行到定义变量的语句时,
//操作系统会根据变量的类型给变量分配对应大小的空间
int a = 10;
//通过变量名是可以直接操作对应的内存空间的
a = 520;
printf("a = %d\n", a);//520
//通过 & 可以获取变量的地址 使用 %p 输出
printf("&a = %p\n", &a);
//可以定义一个指针来保存变量的地址
//定义指针的格式 : 数据类型 *指针变量名;
int *p;
p = &a; //p保存了a的地址 我们称之为 p只向a
printf("p = %p\n", p);
//当指针保存了变量的地址后
//就可以通过指针访问该变量对应的内存空间中的内容了
*p = 1314;
printf("*p = %d, a = %d\n", *p, a); //1314 1314
//不能使用普通的变量来保存地址
//long value = &a;//保存是可以的
//printf("value = %#lx\n", value);
//*value = 1234;//但是普通变量不能做 * 操作
//指针只能保存已经分配了的地址 由操作系统分的或者malloc分的
//int *p2 = 0x12345678; //这是在给 p2 赋值 不是 给 *p2赋值
//printf("p2 = %p\n", p2);
//*p2 = 100;//即使保存了 操作也很危险 错误是不可预知的
//指针的类型的作用:决定指针从保存的地址开始能访问几个字节
//int * 指针能访问4个字节
//char * 指针只能访问保存的那一个字节
//一般情况下 指针的类型要和只向的变量的类型保持一致
//可以在一行中定义多个指针 但是下面的用法是错误的
//int *p3,p4; //这种写法 p3是指针 p4就是普通的int变量
int *p3, *p4; //这种写法 两个才都是指针
//定义指针如果没有初始化 里面也是随机值
//也就是说只向了一个随机的地址,这种指针称之为 ----野指针
//野指针是有害的
//int *p5;
//printf("p5 = %p\n", p5);
//*p5 = 100;//对野指针取*操作 错误是不可预知的
//如果定义指针时不知道指向谁 但是又想防止野指针
//可以用 NULL 来给指针初始化 NULL的本质是 (void *)0
int *p6 = NULL; //叫做空指针
*p6 = 1314; //对NULL取*操作一定是段错误
return 0;
}
1.5 指针变量的大小
32位系统中 指针的大小是4字节
64位系统中 指针的大小是8字节
#include <stdio.h>
int main(int argc, const char *argv[])
{
char *p1;
short *p2;
int *p3;
long *p4;
long long *p5;
float *p6;
double *p7;
printf("sizeof(char *) = %ld sizeof(p1) = %ld\n", sizeof(char *), sizeof(p1));
printf("sizeof(short *) = %ld sizeof(p2) = %ld\n", sizeof(short *), sizeof(p2));
printf("sizeof(int *) = %ld sizeof(p3) = %ld\n", sizeof(int *), sizeof(p3));
printf("sizeof(long *) = %ld sizeof(p4) = %ld\n", sizeof(long *), sizeof(p4));
printf("sizeof(long long *) = %ld sizeof(p5) = %ld\n", sizeof(long long *), sizeof(p5));
printf("sizeof(float *) = %ld sizeof(p6) = %ld\n", sizeof(float *), sizeof(p6));
printf("sizeof(double *) = %ld sizeof(p7) = %ld\n", sizeof(double *), sizeof(p7));
return 0;
}
1.6 指针的运算
指针里面存的都是地址编号,所以,指针的运算,本质就是地址作为运算量来参与运算。
既然是地址的运算,能做的操作就是有限的了。
只有相同类型的指针之间做运算,才是有意义的。
指针能做的运算
算数运算 + - ++ –
关系运算 > < == !=
赋值运算 =
#include <stdio.h>
int main(int argc, const char *argv[])
{
//指针+一个整数n 表示加上n个指针的数据类型的大小
int s[5] = {10,20,30,40,50};
int *p1 = &s[0];
int *p2 = p1+2;
printf("p1 = %p p2 = %p\n", p1, p2);//相差8 2*sizeof(int)
printf("*p1 = %d *p2 = %d\n", *p1, *p2);//10 30
//指针的减法
//两个指针做差:结果是相差的数据类型的个数 并不是字节数!!!
int *p3 = &s[0];
int *p4 = &s[3];
printf("p3 = %p p4 = %p\n", p3, p4);//相差 12
printf("p4-p3 = %ld\n", p4-p3); //得到的是 3 表示相差 3个int
//指针的强转是安全的 因为指针的大小都是一样的
short *p5 = (short *)&s[0];
short *p6 = (short *)&s[3];
printf("p5 = %p p6 = %p\n", p5, p6);//相差 12
printf("p6-p5 = %ld\n", p6-p5);//6 表示相差 6个short
//要注意下面的用法
//int s[5] = {10,20,30,40,50};
int *p7 = &s[0];
int value1 = *++p7; //先算 ++p7 相当于p7的指向向后移动了一个int 然后对新的p7取*
printf("value1 = %d p7 = %p\n", value1, p7);//20 &s[1]
int *p8 = &s[0];
int value2 = *p8++; //先算p8++ 但是注意 取*是对 p8++这个表达式的结果取*
//表达式的结果是 p8 加之前的值 也就是 s[0] 的地址
printf("value2 = %d p8 = %p\n", value2, p8);//10 &s[1]
int *p9 = &s[0];
int value3 = (*p9)++; //这种写法 等价与 int value3 = s[0]++;
printf("value3 = %d p9 = %p\n", value3, p9);//10 &s[0]
printf("s[0] = %d\n", s[0]);// 11
//指针的关系运算
if(p3 < p4){
printf("yes\n");
}else if(p3 > p4){
printf("no\n");
}else if(p3 == p4){
printf("p3 == p4\n");
}
//指针的赋值运算
//指针变量本质也是变量 是变量 就允许进行赋值运算
int a = 10;
int b = 20;
int *pp1 = &a;
int *pp2 = &b;
pp1 = pp2;
return 0;
}
思考,下面的代码会输出什么?
int *p = NULL;
printf("%d %d %d\n", p+1, p, (p+1)-p); //4 0 1
编码
有一个以空格为分隔符的字符串。
“this is a book”
要求以单词为单位进行翻转。
“book a is this”
//思路
//1.先整体翻转
//2.再以单词为单位,每个单词翻转
#include <stdio.h>
int main(){
char s[64] = "this is a book";
printf("整体翻转前:[%s]\n", s);
//1.先整体翻转
int i = 0;
while(s[i])i++;
i--;
int j = 0;
char temp = 0;
while(j < i){
temp = s[i];
s[i] = s[j];
s[j] = temp;
i--;
j++;
}
printf("整体翻转后:[%s]\n", s);
//2.再以单词为单位翻转
i = 0;
j = 0;
int k = 0;
while(1){
while(s[i] != ' ' && s[i] != '\0'){
i++;
}
k = i;//k用来记录当前单词的后一个字符
i--;
while(j < i){
temp = s[j];
s[j] = s[i];
s[i] = temp;
j++;
i--;
}
if(s[k] == '\0'){
break;
}
i = k+1;
j = k+1;
}
printf("完全翻转后:[%s]\n", s);
return 0;
}
2、大小端存储
练习:
定义一个变量a,类型为int,里面存储8888;
定义一个指针p,类型为int *,指向变量a,通过指针p将a的数据改成 0x12345678;
定义一个指针q,类型为 char *, 也指向变量a
分别使用 %#x 输出 *q *(q+1) *(q+2) *(q+3) 的值。
#include <stdio.h>
int main(){
int a = 8888;
int *p = &a;
*p = 0x12345678;
char *q = (char *)&a;
printf("*(q+0) = %#x\n", *(q+0));
printf("*(q+1) = %#x\n", *(q+1));
printf("*(q+2) = %#x\n", *(q+2));
printf("*(q+3) = %#x\n", *(q+3));
return 0;
}
执行结果:
2.1 大小端存储的问题
根据CPU和操作系统的不同,对多字节的数据存储方式也不同。
小端存储:地址低位存储数据低位 地址高位存储数据高位
大端存储:地址低位存储数据高位 地址高位存储数据低位
请编写一个简单的程序,判断你使用的主机是大端存储还是小端存储?
#include <stdio.h>
int main(){
int a = 0x12345678;
char *p = (char *)&a;//&a 是地址地位
if(0x78 == *p){
printf("小端\n");
}else if(0x12 == *p){
printf("大端\n");
}
return 0;
}
思考:
在小端存储的主机下,下面的代码会输出什么?
int x = 0x41424344;
printf("%s\n", &x); // DCBA+不确定的值
2.2 指针和一维数组
#include <stdio.h>
int main(int argc, const char *argv[])
{
int s[5] = {10, 20, 30, 40, 50};
//数组名就是数组的首地址 是一个地址常量
//操作空间和数组的类型是一致的
printf("s = %p\n", s);
printf("s+1 = %p\n", s+1);//相差1个int
//研究 数组名[下标] 访问成员的本质
printf("s[0] = %d *(s+0) = %d\n", s[0], *(s+0));//10 10
printf("s[1] = %d *(s+1) = %d\n", s[1], *(s+1));//20 20
printf("s[2] = %d *(s+2) = %d\n", s[2], *(s+2));//30 30
//也就是说 数组名[下标] 方式访问成员 本质是
//以数组首地址为基准 指针的偏移取数据
// s[i] <==> *(s+i)
//可以定义一个指针指向一位数组
//一般保证操作空间一致 我们都定义和数组类型相同的指针来保存地址
//指针保存数组首地址有下面的写法
int *p1 = s; //数组名就是数组首地址 --最常用的写法
int *p2 = &s[0]; //这样也可以
int *p3 = &s;//这种写法 保存的也是数组的首地址
//但是对数组名取地址 指针操作空间就变了 后面数组指针时再说
//一般不使用这种写法
printf("p1 = %p p2 = %p p3 = %p\n", p1, p2, p3);//一样的
//当指针指向一维数组后 有下面的等价关系
// s[i] <==> *(s+i) <==> *(p+i) <==> p[i]
//一维数组的遍历
int i = 0;
for(i = 0; i < 5; i++){
//printf("%d ", s[i]);
//printf("%d ", *(s+i));
//printf("%d ", *(p1+i));
printf("%d ", p1[i]);
}
printf("\n");
//指针和数组名的区别
//数组名是常量 不能被赋值 也不能++
//指针是变量 可以被重新赋值 也可以++
//关于指针 ++ 操作的意义
int *p4 = s;
for(i = 0; i < 5; i++){
printf("%d ", *p4);
p4++;
}
printf("\n");
return 0;
}
练习:
1.思考:在32位小端存储的主机上,下面的代码会输出什么?
int s[5] = {1,2,3,4,5};
int *p = (int *)((int)s+1);
printf("%x\n", *p);// 2000000
2.使用指针实现 strlen 函数的功能
#include <stdio.h>
int main(int argc, const char *argv[])
{
char s[32] = "hello world";
int count = 0;
#if 0
char *p = s;
while(*p != '\0'){
count++;
p++;
}
#endif
#if 0
int i = 0;
char *p = s;
while(*(p+i)){
count++;
i++;
}
#endif
#if 0
int i = 0;
char *p = s;
while(p[i]){
count++;
i++;
}
#endif
//char s[32] = "hello world";
char *p = s;
while(*p++) count++;
//上面的循环结束时 p指向谁字符串s的'\0'的后面一位
printf("count = %d\n", count);
return 0;
}
3.使用指针实现 strcpy 函数的功能
#include <stdio.h>
int main(int argc, const char *argv[])
{
char s1[32] = "hello";
char s2[32] = "beijing";
char *p = s1;
char *q = s2;
#if 0
while(*q != '\0'){
*p = *q;
p++;
q++;
}
#endif
while(*q){
*p++ = *q++;
}
*p = *q; // '\0'也得拷贝
//因为前面的操作中 p和q的指向已经变了 不是指向s1和s2的首地址了
//所以需要重置一下指向
p = s1;
q = s2;
printf("%s\n", p);
printf("%s\n", q);
return 0;
}
4.使用指针实现 strcat 函数的功能
#include <stdio.h>
int main(int argc, const char *argv[])
{
char s1[32] = "hello";
char s2[32] = "world";
char *p = s1;
char *q = s2;
while(*p++);
//上面循环结束时 p指向 s1的'\0'的后一位
p--; //让p指向s1的 '\0'
//依次追加
while(*q){
*p++ = *q++;
}
*p = *q;//将 s2 的'\0'也追加给s1
printf("s1 = [%s]\n", s1);
printf("s2 = [%s]\n", s2);
return 0;
}
2.3 指针和二维数组
#include <stdio.h>
int main(int argc, const char *argv[])
{
int s[3][4] = {{1,2,3,4},
{5,6,7,8},
{9,10,11,12}};
//二维数组的数组名也是一个地址常量 是数组的首地址
printf("s = %p\n", s);
printf("s+1 = %p\n", s+1);//相差4个int 也就是一行元素
//二维数组的数组名操作空间是一整行元素 叫做行指针
//对行指针取一个*操作 相当于对指针的降维
//把操作空间是一整行的指针降维成操作空间是一个元素的指针
printf("*s = %p\n", *s);
printf("*s+1 = %p\n", *s+1);//相差1个int
//对降维后的指针再取*操作 才是操作数据
printf("**s = %d\n", **s);//1
printf("*(*s+1) = %d\n", *(*s+1));//2
printf("*(*(s+1)+1) = %d\n", *(*(s+1)+1));//6
printf("*(s[2]+2) = %d\n", *(s[2]+2));//11
//也就是说 有如下的等价关系
//s[i][j] <==> *(*(s+i)+j) <==> *(s[i]+j)
//注意:由于二维数组的数组名是一个行指针 操作空间已经超过基本类型了
//所以不能使用普通的指针来保存二维数组的首地址
//int *p = s;
//printf("p = %p\n", p);
//*(p+5) = 1314;
//printf("%d\n",s[1][1]);
//即使保存了 也只能按照单个元素操作 不能按照整行操作
//因为 p 就是一个普通的 int * 指针
//如果想保存二维数组的首地址 需要用到 数组指针
//二维数组的遍历
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 ", *(*(s+i)+j));
}
printf("\n");
}
return 0;
}
2.4 数组指针
本质是一个指针,指向一个二维数组,也叫作行指针。
数组指针多用于二维数组作为函数的参数传递时。
格式:
数据类型 (*数组指针名)[列宽];
例:
#include <stdio.h>
int main(int argc, const char *argv[])
{
int s[3][4] = {{1,2,3,4},
{5,6,7,8},
{9,10,11,12}};
//定义了一个数组指针p 指向二维数组s
int (*p)[4] = s;
//当指针保存了二维数组的首地址后 操作就和 数组名操作是一样的了
//有下面的等价关系
// s[i][j] <==> *(s[i]+j) <==> *(*(s+i)+j) <==>
// p[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 ", *(*(s+i)+j));
//printf("%d ", p[i][j]);
//printf("%d ", *(p[i]+j));
printf("%d ", *(*(p+i)+j));
}
printf("\n");
}
//p和s的区别
//p 是指针 是变量
//s 是数组名 是常量
return 0;
}
之所以不能对一维数组数组名取地址的原因:
#include <stdio.h>
int main(int argc, const char *argv[])
{
int s[5] = {1,2,3,4,5};
//对数组名s 取&操作 相当于 对指针的升维操作
//把本来操作空间是一个元素的指针 升维 成操作空间是一行的指针
//而我们的p就是一个普通的 int * 指针, 操作空间只能是一个元素
//所以 编译时 类型不匹配 会报警告
//int *p = &s;
//printf("%d\n", p[3]); //4
//可以使用数组指针来消除这个警告
int (*p)[5] = &s;
//但是注意 虽然警告消除了 但是此时的p就没有太大的意义了
//因为p的操作空间是一行 而我们的一维数组 一共就只有1行
//所以 p+1 就已经越界访问了
//所以不要对 一维数组的数组名取地址 !!!
return 0;
}
2.5 指针数组
本质是一个数组,数组中每个元素都是一个指针。
格式:
数据类型 *指针数组名[下标];
例:
#include <stdio.h>
int main(int argc, const char *argv[])
{
//可以使用二维数组来处理多个字符串
char name[4][64] = {
"zhangsan",
"lisi",
"fulajimier.fulajimiluoweiqi.pujing",
"zhaoliu"
};
printf("%s\n", name[0]);
printf("%s\n", name[1]);
printf("%s\n", name[2]);
printf("%s\n", name[3]);
//但是这种处理方式不太好 因为需要以最长的字符串为准 会造成很多空间上的浪费
printf("-------------------------------\n");
//也可以使用指针数组来处理
//定义了一个指针数组,数组名叫 p_name
//数组中共有4个元素 每个元素都是一个 char * 类型的指针
char *p_name[4] = {NULL};
//当取出数组的元素后 操作就和操作普通的 char * 指针 是一样的了
p_name[0] = "zhangsan";
p_name[1] = "lisi";
p_name[2] = "fulajimier.fulajimiluoweiqi.pujing";
p_name[3] = "zhaoliu";
printf("%s\n", p_name[0]);
printf("%s\n", p_name[1]);
printf("%s\n", p_name[2]);
printf("%s\n", p_name[3]);
printf("sizeof(p_name) = %ld\n", sizeof(p_name));//32 == 4 * sizeof(char *)
return 0;
}
2.6 指针和字符串
虚拟内存分区
#include <stdio.h>
int main(int argc, const char *argv[])
{
//可以定义一个指针 直接指向字符串常量
char *p1 = "hello world"; //字符串常量在字符串常量区
//是只读的 不能修改的
printf("p1 = %s\n", p1);//读取没问题
//*p1 = 'H'; //错误的 修改就段错误
char *p2 = "hello world";
printf("p1 = %p p2 = %p\n", p1, p2);//不管定义多少个指针
//只要指向同一个字符串常量 那么地址就是一样的
printf("---------------------------------\n");
//也可以定义一个数组来保存字符串
//s1是局部的 在栈区 由操作系统分配空间
//相当于用字符串常量 "hello world" 来初始化数组
char s1[32] = "hello world";
//栈区的内容是允许修改的
printf("s1 = %s\n", s1);//hello world
*s1 = 'H';
printf("s1 = %s\n", s1);//Hello world
char s2[32] = "hello world";
printf("s1 = %p s2 = %p\n", s1, s2);//不一样
return 0;
}
2.7 二级指针
二级指针是用来保存一级指针的地址
二级指针多用于将一级指针作为函数的参数传递时。
变量、一级指针、二级指针的关系图:
例:
#include <stdio.h>
int main(int argc, const char *argv[])
{
int a = 10;
int *p = &a;
int **q = &p;
//上面三步执行完 有如下的等价关系
// a <==> *p <==> **q
// &a <==> p <==> *q
// &p <==> q
printf("a = %d *p = %d **q = %d\n", a, *p, **q);//一样的
printf("&a = %p p = %p *q = %p\n", &a, p, *q);//一样的
printf("&p = %p q = %p\n", &p, q);//一样的
**q = 520;//正确的 给a赋值
printf("a = %d *p = %d **q = %d\n", a, *p, **q);//一样的
//不能使用一级指针来保存一级指针的地址
//int *temp = &p;
//printf("temp = %p\n", temp);
//**temp = 1314;//即使保存了 也没有用 因为一级指针不能取 ** 操作
return 0;
}
编码:
自己编写代码实现 atoi() 函数的功能 ---- 字符串转整型
//思路
//"1234" --> 1234
//((1*10+2)*10+3)*10+4
#include <stdio.h>
int main(){
char s[10] = "4399";
int num = 0;
char *p = s;
while(*p != '\0'){
num *= 10;
num += (*p-'0');
p++;
}
printf("%d\n", num);
return 0;
}
//上面三步执行完 有如下的等价关系
// a <==> *p <==> **q
// &a <==> p <==> *q
// &p <==> q
printf("a = %d *p = %d **q = %d\n", a, *p, **q);//一样的
printf("&a = %p p = %p *q = %p\n", &a, p, *q);//一样的
printf("&p = %p q = %p\n", &p, q);//一样的
**q = 520;//正确的 给a赋值
printf("a = %d *p = %d **q = %d\n", a, *p, **q);//一样的
//不能使用一级指针来保存一级指针的地址
//int *temp = &p;
//printf("temp = %p\n", temp);
//**temp = 1314;//即使保存了 也没有用 因为一级指针不能取 ** 操作
return 0;
}
编码:
自己编写代码实现 atoi() 函数的功能 ---- 字符串转整型
//思路
//“1234” --> 1234
//((1*10+2)*10+3)*10+4
#include <stdio.h>
int main(){
char s[10] = “4399”;
int num = 0;
char *p = s;
while(*p != ‘\0’){
num *= 10;
num += (*p-‘0’);
p++;
}
printf(“%d\n”, num);
return 0;
}