携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第11天,点击查看活动详情
前言
学习C语言,不得不学到的就是指针,甚至可以这么说:指针是C语言的精髓之所在。
本文就来分享一波作者的C指针学习见解与心得。本篇属于初阶第三篇,主要讲解了指针运算与const修饰指针的一些内容。后续还有初阶的其他内容以及进阶内容,可以期待一下。
笔者水平有限,难免存在纰漏,欢迎指正交流。
指针运算
指针的关系运算
说明:
指针就是地址,对指针进行比较比的也就是地址的高低。
例子:
#define N_VALUES 5
for(vp = &values[N_VALUES]; vp > &values[0]; )
{
*--vp = 0;
}
我们知道,数组的下标是从0开始的,那么这里的&values[N_VALUES]
是不是越界了呢?非也,越界指的是访问越界,这里没有访问那块空间,只是取了它的地址,没有什么问题。
这个循环的意思就是,把数组values的所有元素的值都赋为0,不过要注意这里的自减操作符是放在vp前面的,而且自减操作符优先级高于*。
修改一下:
#define N_VALUES 5
for(vp = &values[N_VALUES-1]; vp >= &values[0]; vp--)
{
*vp = 0;
}
实际在绝大部分的编译器上是可以顺利完成任务的,然而我们还是应该避免这样写,因为标准并不保证它可行。
标准规定:
允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。
指针+-整数
指针+-整数就是发生地址偏移,根据指针类型决定偏移步长(类型所占大小),比如对于char*
一步长为1字节,int*
一步长为4字节,这时候加或减的整数就是步长数,char*p;
,p+2
就是向高地址偏移2步长也就是2字节。当然指针变量可以自增自减以改变所存储的地址的值。
++比*优先级高!!*vp++ = 0;
相当于*vp = 0; vp++;
。而(*vp)++
就是把vp指向的值自增1。
#define N_VALUES 5
int main()
{
float values[N_VALUES];
float *vp;
//指针+-整数;指针的关系运算
for (vp = &values[0]; vp < &values[N_VALUES];)
{
*vp++ = 0;
}
}
你可能会奇怪,这数组只有五个元素,下标为5不就越界了吗,为什么还能出现&values[5]呢,实际上[]是下标引用操作符,
&values[5]
<->&*(values + 5)
,&*一抵消,剩下values + 5
就是按着float型指针偏移了5步长后得到的指针(地址),也是float类型,要是解引用的话也能访问。我们所说的越界访问是可以发生的,而我们一般是不希望它发生的,就像我们定义数组会定义数组长度,系统按我们的要求划分一块连续空间给我们使用,而这块空间后面的空间就是未分配给我们的、没有权限的空间,可是依靠指针我们还是能够随时访问这些空间。(指针权限太大了,简直是一把双刃剑)
arr[5]实际上就是按照指针类型在原数组后面再找了一块空间进行访问,这块空间存的是什么值我们是无法预知的。
指针相减
|指针-指针|(注意是绝对值)得到指针间元素个数,不过要注意是在同一块连续空间(如数组)上同类型指针才能进行相减。
比如:
int my_strlen(char *s) {
char *p = s;
while(*p != '\0' )
p++;
return p-s;//双指针计算字符串字符个数
}
那有没有指针+指针呢?没有,主要是没有实际意义,就好比如生活中有日期-日期,日期+-天数,但是没有日期+日期,因为没有意义。
数组和指针的关系
数组名表示的是数组首元素的地址。(2种情况除外,数组章节会讲解)
那么这样写代码是可行的:
int arr[10] = {1,2,3,4,5,6,7,8,9,0};
int *p = arr;//p存放的是数组首元素的地址
用int*p存放数组名arr即首元素地址,则 p+i 其实计算的是数组 arr 下标为i的地址。
那我们就可以直接通过指针来访问数组,如*(p + i)就是arr[i]。
也就是数组的指针表示:arr[i] <->*(arr + i)。
指针和数组访问互通
实际上,数组和指针都可以互相用对方的方式来表示。
比如:
#include<stdio.h>
#include<string.h>
#define N 10
int main() {
const char *str = "abcdef"; //str指针变量在栈上保存,“abcdef”在字符常量区,不可被修改
char arr[] = "abcdef"; //整个数组都在栈上保存,可以被修改,这部分可以局部测试一下
printf("以指针的形式访问指针和以数组下标的形式访问指针\n");
int len = strlen(str);
for (int i = 0; i < len; i++)
{
printf("%c\t", *(str + i));
printf("%c \n", str[i]);
}
printf("\n");
printf("以指针的形式访问数组和以数组下标的形式访问数组\n");
len = strlen(arr);
for (int i = 0; i < len; i++)
{
printf("%c\t", *(arr + i));
printf("%c \n", arr[i]);
}
printf("\n");
return 0;
}
指针和数组指向或者表示一块空间的时候,访问方式是可以互通的,具有相似性。但是具有相似性,不代表它们是一个东西或者具有相关性。
之所以这样设计,很有可能和数组传参的设计有关,这部分内容将在《指针进阶》的博文讲到。
指针和数组的区别
指针的强制转化
强制类型转化,改变的是对特定内容的看待方式,在C中,就是只改变其类型,不会改变数据本身。
对于强转,一是为了编译器不报警,二是为了用户能够明确类型,三是为了改变看待数据的方式。
例1
例2
int main()
{
int a[4] = { 1, 2, 3, 4 };
int *ptr1 = (int *)(&a + 1);
int *ptr2 = (int *)((int)a + 1);
printf("%x,%x\n", ptr1[-1], *ptr2);
return 0;
}
const修饰下的指针
常量指针
样式:
const 类型 * ptr
如const int * ptr,而int const* ptr这样写也没问题。
为什么要叫常量指针?意味着它指向的是常量吗?
比如
int a = 10;
const int* ptr = &a;
这样一来就不能通过解引用ptr来改变a的值了,也就是对于该指针来说指向的是常量(不可变更),实际上并不是说a就是常量了。
举个例子:
一户人家为了防盗,特地锁好门,这样就限制了从门进入这一可能,窃贼不就没办法通过门进入了嘛,自以为万无一失,却没想到窃贼从窗户翻入,“条条大路通罗马”嘛(笑)。
指针常量
样式:
类型* const ptr
如int* const ptr
为什么要叫指针常量呢?真的变成常量了吗?
比如
int a = 10;
int const*ptr = &a;
这样一来ptr存的地址值不能改变了,也就是ptr只能指向a了。
小总结
const修饰指针变量的时候:
const如果放在*的左边,修饰的是指针指向的内容,保证指针指向的内容不能通过指针来改变,但是指针变量本身的内容可变。
const如果放在*的右边,修饰的是指针变量本身,保证了指针变量的内容不能修改,但是指针指向的内容,可以通过指针改变。
常量指针常量
一听到这个名字就让人迷惑,这是个啥呀?其实也就是上面讲的两种形式的“合体版”。
样式:
const 类型 * const ptr
综合了常量指针和指针常量的特点,也就是既不能改变指针的内容,又不能改变指针指向的内容。
不妨举个日常生活中的例子来加深理解🤗:
是不是瞬间就get到了呢?还是得举例子帮助理解呀。
以上就是本文全部内容了,感谢观看,你的支持就是对我最大的鼓励~
今天的文章[深入浅出C语言]初探指针——指针运算与const修饰指针分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/20353.html