C语言指针讲解(三)
谨记
哎,我......觉得你变了,我怎么变了?你以前是那么的听话,那么的在乎我,总是想象美好的生活而奋斗,遇到困难不断的去尝试克服,生活的态度总是那样积极向上的。而如今的你,变得总是感慨这也不好,那也不好,总是觉得这也不适合你,那也不适合你,不知道自己要什么,自己的理想信念就不复存在了。你错了,其实我......并没有改变,而改变的是这个社会,我只是让社会虐待得体无完肤。那......你还能回到从前的你吗?我想......我回不到从前的我了,不过我想变得比从前的我更优秀,等着......我,好吗?,好,我等着你,然后我们一起前行......。相信大家读完这段话后,就觉得这大概应该是一队情侣之间的对话,其实不是,这是未来的你和现在的你的对话。人生亦是如此。
前言
本篇文章将讲解C语言指针的最后一点内容,const和指针,以及字符指针等相关内容,对于指针,常用的都已经介绍了,将来我们学了函数过后,对指针我们又的有一个重新的认识,后面也会将讲解指针和函数之间的关系。
const和指针
我们都知道,在我们以前申明一个变量,并且为其赋值,当我们需要改变变量的值的时候,就在给它赋值一次就行,比如: int a = 0; 这是申明一个变量a并为其赋值为0,然后如果程序需要,那么我们可以a = 20;这样就能改变变量的值为20.
不过在某一天某一个特定的场景,我们需要一个值永远也不要改变,在这个变量申明的时候给他一个初始值,之后他的值就不能改变了,那么这个时候我们就需要用一个C语言中的一个关键字const。
在C语言中,关键字const修饰变量,可以使得变量常量化。const修饰基本简单类型(非指针)时,很容易理解,就是变量的值不允许修改,比如下面的表达式:
int const m = 20;
const int m = 30;
上面这两种写法,变量m的值都不会被修改。
如果用const修饰指针变量,会使得指针变量常量化,但要注意const的位置。这里一般就3种情况:
- 常量化指针目标表达式
- 常量化指针变量
- 常量化指针变量及其目标表达式
常量化指针目标表达式
常量化指针目标是限制通过指针改变其目标的数值。一般说明形式如下:
const <数据类型>*<指针变量名称>=[ <指针运算表达式>]
我们可以通过一个示例代码来看
int main(int argc, char *argv[])
{
int m = 100, n = 200;
const int *p;
p = &m;
printf("1) %d %p %d %p\n", m, &m, *p, p);
m++;
printf("2) %d %p %d %p\n", m, &m, *p, p);
p = &n;
printf("3) %d %p %d %p\n", n, &n, *p, p);
#ifdef _DEBUG_
*p = n;
printf("4) %d %p %d %p\n", m, &m, *p, p);
#endif
return 0;
}
输出结果:
1) 100 0x7fff5fbff83c 100 0x7fff5fbff83c
2) 101 0x7fff5fbff83c 101 0x7fff5fbff83c
3) 200 0x7fff5fbff838 200 0x7fff5fbff838
Program ended with exit code: 0
这种情况下,编译程序时,没有调试debug任何选项时,相当于没有定义宏_DEBUG_,程序可以正常执行。可以看出,const int *p这种写法,允许修改p,指针p可以改变指向。
总之:“const int *p”,const的作用,限制了通过指针去修改指针的目标。但还可以直接修改m。
常量化指针变量
常量化指针变量,使得指针变量存储的地址值不能修改。一般说明形式如下:
<数据类型> *const <指针变量名称>= <指针运算表达式>;
int main(int argc, char *argv[])
{
int m = 100, n = 200;
int * const p = &m;
printf("1) %d %p %d %p\n", m, &m, *p, p);
m++;
printf("2) %d %p %d %p\n", m, &m, *p, p);
*p = n;
printf("3) %d %p %d %p\n", m, &m, *p, p);
#ifdef _DEBUG_
p = &n;
printf("4) %d %p %d %p\n", n, &n, *p, p);
#endif
return 0;
}
输出结果:
1) 100 0x7fff5fbff83c 100 0x7fff5fbff83c
2) 101 0x7fff5fbff83c 101 0x7fff5fbff83c
3) 200 0x7fff5fbff83c 200 0x7fff5fbff83c
Program ended with exit code: 0
这种情况下,编译程序时,即没有定义宏_DEBUG_,程序可以正常执行。因此,int *const p这种写法, *p可以被重新赋值,即允许通过指针p改变指针的目标值。
总之,“int * const p”,const的作用,使得指针变量的指向不能修改。但可以通过 * 指针变量名称去修改指针所指向变量的数值。
常量化指针变量及其目标表达式
一般说明形式如下:
const <数据类型> * const <指针变量名> = <指针运算表达式> ;
常量化指针变量及其目标表达式,使得既不可以修改<指针变量>的地址,也不可以通过*<指针变量名称>修改指针所指向变量的值。
void指针
void型的指针变量是一种不确定数据类型的指针变量,它可以通过强制类型转换让该变量指向任何数据类型的变量或数组。
一般形式为
void *<指针变量名称> ;
对于void型的指针变量,实际使用时,一般需通过强制类型转换才能使void型指针变量得到具体变量或数组地址。在没有强制类型转换之前,void型指针变量不能进行任何指针的算术运算。
int main(int argc, const char * argv[]) {
int m = 100;
void *p;
p = (void *)&m;
printf("%d %p %d %p\n", m, &m, *p, p);
return 0;
}
输出结果:这个程序诶报错,main.c:18:36: Argument type 'void' is incomplete,如果把上面的打印语句替换成这样 printf("%d %p %d %p\n", m, &m, *(int *)p, p);这样输出结果为:
100 0x7fff5fbff83c 100 0x7fff5fbff83c
Program ended with exit code: 0
在该程序中,使用void指针时,应该首先进行类型转换。尤其是引用指针的目标值,若没有经过转换,void *相当于类型不确定,只知道指针目标的起始地址,不知道其占用的字节数,没办法处理,是编译错误。类似的道理,如果void指针没有转换成具体的指针类型,也不可以进行指针的运算,因为,没办法决定以什么为单位来偏移。
对于void型的指针变量,实际使用时,一般需通过强制类型转换才能使void型指针变量进行指针的运算或得到其目标的值。
字符指针
字符指针就是存储字符变量的地址。在这里,重点介绍字符指针,是为了讲解字符指针在处理字符串及字符指针数组时的用法。
例子:输入一个字符串,倒序输出。
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[])
{
char s[10];
char *p, *q, t;
printf("input a string:");
scanf("%s", s);
p = s;
q = s + strlen(s) - 1;
while (p < q)
{
t = *p;
*p = *q;
*q = t;
p++;
q--;
}
printf ("%s\n", s);
return 0;
}
输出结果:
input a string:abc
cba
Program ended with exit code: 0
在该程序中,使用字符串为字符数组赋值。指针p指向字符串的第一个字符,指针q指向字符串的最后一个字符(结束符\0之前的字符),通过p++,q --来让指针分别指向待交换的两个字符。
初始化字符指针时,可以把内存中字符串的首地址赋予指针。这时,并不是把该字符串复制到指针中,而是让指针指向字符串的起始字符。
#include <stdio.h>
int main(int argc, char *argv[])
{
char *s = "Welcome!";
printf("%s\n",s);
s++;
printf("%s\n",s);
#ifdef _DEBUG_
(*s)++;
printf("%s\n",s);
#endif
return 0;
}
输出结果:
Welcome! Welcome!
Program ended with exit code: 0
这就是指向字符串的指针。请读者一定要注意,第一种方式是把字符串的内容保存在数组里,即数组的每一个元素保存一个字符。但指针变量只有4个字节,只能用来保存地址。因此,指针变量里的值是字符串“Welcome!”的首地址,而字符串本身存储在内存的其他地方。
注意
在使用字符指针指向字符数组时,有以下两点需要注意。
① 虽然数组名也表示数组的首地址,但由于数组名为指针常量,其值是不能改变的(不能进行自加、自减、赋值等操作)。如果把字符数组的首地址赋给一个字符指针变量,就可以移动这个指针来访问或修改数组中的字符。
② 在使用scanf时,其参数前要加上取地址符 &,表明是一个地址。如果指针变量的值已经是一个字符数组的首地址,那么可以直接把指针变量作为参数而不需要再加上取地址符&。
当程序中增加了语句“(*s)++”后,发现产生了段错误。在这里,读者要特别注意,当一个字符指针初始化为指向一个字符串常量时,不能对字符指针变量的目标赋值。从这个角度看,“char *s = "Welcome!"”相当于 “const char *s = "Welcome!"”,只是省略了const。
温馨提示:当一个字符指针初始化为指向一个字符串常量时,不能对字符指针变量的目标赋值。
字符指针数组
若数组中存储了若干个字符串的地址,则这样的数组就叫做字符指针数组。
示例代码:
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[])
{
char s1[] = "Welcome";
char s2[] = "to";
char s3[] = "farsight";
char* a1[3] = {s1, s2, s3};
char *a2[3] = {"Welcome", "to", "farsight"};
char **p;
int i;
p = a1;
printf("array1:%s %s %s\n", a1[0], a1[1], a1[2]);
for (i = 0; i < sizeof(a1) / sizeof(char *); i++)
printf("%s ", *(p+i));
printf("\n");
p = a2;
printf("array2:%s %s %s\n", a2[0], a2[1], a2[2]);
for (i = 0; i < sizeof(a1) / sizeof(char *); i++)
printf("%s ", *(p+i));
printf("\n");
return 0;
}
输出结果:
array1:Welcome to farsight
Welcome to farsight
array2:Welcome to farsight
Welcome to farsight
Program ended with exit code: 0
在该程序中,数组a1就是字符指针数组,分别存储了字符数组s1,s2,s3的起始地址。即数组a1含有3个指针,分别指向s1,s2,s3存储的3个字符串。类似的数组a2,也是字符指针数组,含有3个字符指针,分别指向了常量串“Welcome”,“to”,“farsight”。
需要注意的是字符指针数组的数组名,即代表数组的起始地址,或者第一个元素的地址。数组的元素已经是字符指针类型了,所以很容易理解,元素的地址就是二级指针。因此该程序中,使用二级指针p来对字符指针数组进行了遍历。
总结
到这篇文章结束为止,我们已经把C语言的重点学了一半了,那么C语言的指针就已经学完了,我希望读者能够认真的学习,希望把指针搞明白。
结尾
希望读者真诚的对待每一件事情,每天都能学到新的知识点,要记住,认识短暂,开心也是过一天,不开心也是一天,无所事事也是一天,小小收获也是一天,所以,你的路你自己选择。