和卿做一个默默无私奉献的程序猿程序员

C语言指针讲解(三)

2017-11-14  本文已影响183人  长风留言

谨记

哎,我......觉得你变了,我怎么变了?你以前是那么的听话,那么的在乎我,总是想象美好的生活而奋斗,遇到困难不断的去尝试克服,生活的态度总是那样积极向上的。而如今的你,变得总是感慨这也不好,那也不好,总是觉得这也不适合你,那也不适合你,不知道自己要什么,自己的理想信念就不复存在了。你错了,其实我......并没有改变,而改变的是这个社会,我只是让社会虐待得体无完肤。那......你还能回到从前的你吗?我想......我回不到从前的我了,不过我想变得比从前的我更优秀,等着......我,好吗?,好,我等着你,然后我们一起前行......。相信大家读完这段话后,就觉得这大概应该是一队情侣之间的对话,其实不是,这是未来的你和现在的你的对话。人生亦是如此。

前言

本篇文章将讲解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语言的指针就已经学完了,我希望读者能够认真的学习,希望把指针搞明白。

结尾

希望读者真诚的对待每一件事情,每天都能学到新的知识点,要记住,认识短暂,开心也是过一天,不开心也是一天,无所事事也是一天,小小收获也是一天,所以,你的路你自己选择。

上一篇 下一篇

猜你喜欢

热点阅读