ARM技术论坛
直播中

记帖MCU

2年用户 137经验值
擅长:嵌入式技术 控制/MCU RF/无线
私信 关注
[讨论]

C语言指针详细解析

概述

指针也就是内存地址,指针变量是用来存放内存地址的变量, 不同类型的指针变量所占用的存储单元长度是相同的,而存放数据的变量因数据的类型不同,所占用的存储空间长度也不同 。有了指针以后,不仅可以对数据本身,也可以对存储数据的变量地址进行操作。

指针是一个占据存储空间的实体在这一段空间起始位置的相对距离值。在C/C++语言中,指针一般被认为是指针变量,指针变量的内容存储的是其指向的对象的首地址,指向的对象可以是变量(指针变量也是变量),数组,函数等占据存储空间的实体。

指针

数据存储在内存中,内存又被分为一块一块的,每一块都有一个特有的编号。而这个编号可以暂时理解为指针,就像房屋的编号。特点的房间可以找到特点的人,例如张三要去找李四,那么就要去到李四家,才能找到李四。

总结一下,其实指针就是变量,用来存放地址的变量(存放在指针中的值都当成地址处理)。

指针运算符

&:取地址运算符&是用来取操作对象的地址,它返回运算对象的内存地址。

*:指针运算符&作用是通过操作对象的地址,获取存储的内容也称为“间接引用操作符”。

示例

#include <stdio.h>
int main()
{
    int a = 10;    //定义一个普通变量a,赋值为10
    int* pa;//定义指针变量pa
    pa = &a;//通过取地址符&,获取a的地址,赋值给指针变量pa
    printf("a的值为:%d,pa的地址为=%p,*pa的值为=%d\n",a,pa,*pa);
    return 0;
}

指针类型

变量有不同的类型,整型,浮点型等等。指针同样是有类型的,定义如下。

char*   pa = NULL;
int*    pb = NULL;
short*  pc = NULL;
long*   pd = NULL;
float*  pe = NULL;
double* pf = NULL;

指针类型的定义方式就是type + * 。其实上面代码中char* 就是为了存放char类型变量的地址,short*就是为了存放short类型变量的地址。其他同样。

示例

#include <stdio.h>
int main()
{
    char   a = 'a';
    int    b = 10;
    short  c = 20;
    long   d = 30;
    float  e = 40.0;
    double f = 50.0;
    char*   pa = NULL;
    int*    pb = NULL;
    short*  pc = NULL;
    long*   pd = NULL;
    float*  pe = NULL;
    double* pf = NULL;
    pa = &a;
    pb = &b;
    pc = &c;
    pd = &d;
    pe = &e;
    pf = &f;
    printf("a的值为:%d,pa的地址为=%p,pa的下一个地址为=%p\n", a, pa, pa + 1);
    printf("b的值为:%d,pb的地址为=%p,pb的下一个地址为=%p\n", b, pb, pb + 1);
    printf("c的值为:%d,pc的地址为=%p,pc的下一个地址为=%p\n", c, pc, pc + 1);
    printf("d的值为:%d,pd的地址为=%p,pd的下一个地址为=%p\n", d, pd, pd + 1);
    printf("e的值为:%f,pe的地址为=%p,pe的下一个地址为=%p\n", e, pe, pe + 1);
    printf("f的值为:%lf,pf的地址为=%p,pf的下一个地址为=%p\n", f, pf, pf + 1);
    return 0;
}

从上述例程得知,指针加1或减1运算,表示指针向前或向后移动一个单元(不同类型的指针,单元长度不同),指针的类型决定了指针向前或者向后走一步有多大距离。

指针变量的自增自减运算。指针加 1 或减 1 运算,表示指针向前或向后移动一个单元(不同类型的指针,单元长度不同)。这个在数组中非常常用。

指针变量加上或减去一个整形数。和第一条类似,具体加几就是向前移动几个单元,减几就是向后移动几个单元。

指针变量的初始化

指针初始化是将变量的地址分配给指针变量的过程,指针变量与其它变量一样,在定义时可以赋值,即初始化。也可以赋值“NULL”或“0”,如果赋值“0”,此时的“0”含义并不是数字“0”,而是 NULL 的字符码值。

指针变量在定义时如果未初始化,那么该指针就是野指针,野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的),其值是随机的,指针变量的值是别的变量的地址,意味着指针指向了一个地址是不确定的变量,此时去解引用就是去访问了一个不确定的地址,所以结果是不可知的。

关系运算

假设有指针pa,pb,那么其关系运算主要有下列三种。

  1. pa > pb,表示 pa 指向的存储地址大于 pb 指向的地址。
  2. pa == pb,表示 pa 和 pb 指向同一个存储单元。
  3. pa == 0 ,表示 pa 是否为空指针。

示例

#include <stdio.h>
int main()
{
    int    a = 10;
    int       b = 20;

    int*    pa  = NULL;
    int*    paa = NULL;
    int*    pb  = NULL;
    int*    pc  = NULL;

    pa  = &a;
    paa = &a;
    pb  = &b;
    printf("pa的地址是%p,pb的地址是%p,",pa,pb);
    if (pa > pb)
        printf("pa指向的存储地址大于pb指向的地址\n");
    else
        printf("pa指向的存储地址小于pb指向的地址\n");

    printf("pa的地址是%p,paa的地址是%p,", pa, paa);
    if (pa == paa)
        printf("pa和paa是否指向同一个存储单元\n");

    printf("pc的地址是%p,", pc);
    if (pc == 0)
        printf("pc是空指针");

    return 0;
}

数组

一维数组

不管什么变量都有地址,数组包含若干个元素,但是每个数组元素也在内存中占用存储单元,所以也有相对应的地址。指针变量既然可以指向变量,同样也可以指向数组元素。

在数组中,数组名即为该数组的首地址,对该指针进行加减,就可以实现指针访问数组元素。

示例

#include <stdio.h>
int main()
{
    int Num[5] = {11,22,33,44,55};
    int* p;
    int* pp;

    p = &Num[0];//指向数组第一个元素,即数组首地址
    pp = &Num;//直接指向数组,数组名即为数组的首地址

    printf("数组的首地址Num=%p\n", Num);
    printf("pp所指向的地址%p\n", pp);
    printf("p所指向的地址是%p,数据是%d\n",p,*p);
    printf("Num所指向的下一个地址是%p,数据是%d\n", Num + 1, *(Num + 1));//数组名即为该数组的首地址,对该指针进行加减,就可以实现指针访问数组元素。
    printf("p所指向的下一个地址是%p,数据是%d\n", p+1, *(p+1));
    return 0;
}

由上述的结果可以得知:

  1. p 指向数组Num的第一个元素,则此操作将 Num第一个元素11,即Num[0] = 11。
  2. 数组名是地址,可以称作数组地址,也可以看成第一个元素的地址,通过+整数可以移动到想要操作的元素。
  3. p+1操作为指针加整数操作,即向前移动一个单元。此时 p + 1 指向Num[0]的下一个元素,即Num[1]。通过p + 整数的操作可以移动到想要操作的元素。
  4. 在 p+整数的操作要考虑边界的问题,如一个数组长度为5,p+6的意义对于数组操作来说没有意义。

二维数组

二维数组其实可以看成是一个矩阵,zai C语言中,定义一个数组num[3][4],可以看成是一个3行4列的矩阵,在内存中每一个位置存储一个数据,用a[i][j]表示。

二维数组实际上就是元素为一维数组的数组,二维数组名可以看做指向其第一个元素(一维数组)的指针。

### 示例

#include <stdio.h>
int main()
{
    int Num[2][3] = {
        {11,22,33},
        {111,222,333}
        };
    int* p;

    p = &Num[0];//指向数组第一个元素,即数组首地址
    //二维数组名可以看做指向其第一个元素(一维数组)的指针,所以一级指针指向大小为一位数组大小
    printf("数组的首地址是%p,一级指针的大小为%d,二级指针所指向数据为%d\n", Num, sizeof(*Num),**Num);
    printf("p所指向的地址是%p,数据是%d\n",p,*p);
    printf("Num所指向的下一个地址是%p,数据是%d\n", Num + 1, **(Num + 1));
    printf("p所指向的第四个地址是%p,数据是%d\n", p+3, *(p+3));
    return 0;
}

字符串指针

对于字符,在计算机内部都是用数字(字符编码)来表示的,而字符串是“字符连续排列”的一种表现。字符串就是每个元素内都存储着字符的一维数组,通常称之为字符数组。

在 C语言中,因为字符数组的元素内存储的都是 char 型的字符,所以字符数组的数据类型是 char 型,因而字符串实际上就是一个 char 型的一维数组。 在 C语言中,可以用两种方法访问一个字符串:

  1. 用字符数组存放一个字符串,然后输出该字符串
  2. 用字符指针指向一个字符串

字符串中包含的字符的个数就是这个字符串的长度。C语言中用字符数组存储字符串时在字符串的末端都要加一个字符“\0”来表示这个字符串的结束,这个“\0”称为字符串结束符。因而在定义字符数组时,数组大小应为要存储的字符串长度的最大值加 1。

示例

#include <stdio.h>
int main()
{
    /*字符数组赋初值*/
    char string1[] = { 'h','e', 'l', 'l', 'o'};
    /*字符数组赋初值添加结束符*/
    char string2[] = { 'h','e', 'l', 'l', 'o','\0' };
    /*字符串赋初值*/
    char string3[] = "hello";
    /*用sizeof()求长度*/
    printf("string1的长度=%d\n", sizeof(string1));//输出出现乱码就是因为字符串结尾并没有结尾符'\0'。
    printf("string2的长度=%d\n", sizeof(string2));//
    printf("string3的长度=%d\n", sizeof(string3));
    /*用printf的%s打印内容*/
    printf("string1的内容=%s\n", string1);
    printf("string2的内容=%s\n", string2);
    printf("string3的内容=%s\n", string3);
    return 0;
}

字符串指针变量本身是一个变量,用于存放字符串的首地址。而字符串本身是存放在以该首地址为首的一块连续的内存空间中并以 \0 作为串的结束。字符数组归根结底还是一个数组,字符串名也可以认为是一个指针。 字符串储存方式:

  1. 字符数组由一个或若干元素组成,每个元素存放一个字符;
  2. 而字符指针变量只存放字符串的首地址,不是整个字符串;

字符串存储位置:

  1. 数组是在内存中开辟了一段空间用于存放字符串;
  2. 字符指针是在文字常量区开辟了一段空间存放字符串,将字符串的首地址赋值给指针变量。### 示例
#include <stdio.h>
int main()
{
    char str[] = "hello world";// 栈(局部)
    char* string = "hello";//文字常量区
    char* string1;
    string1 = "hello world";//字符指针变量另外一种赋值方法
    printf("str=%s,数据大小=%d\n", str, sizeof(str));//数据大小为所保存的字符大小
    printf("string=%s,数据大小=%d\n", string,sizeof(string));//数据大小只是保存的指针大小
    printf("string1=%s,数据大小=%d\n", string1,sizeof(string1));//数据大小只是保存的指针大小
    return 0;
}

由上图可以得知,数组是在内存中开辟了一段空间用于存放字符串,故数组越大,所占的数据大小越大;字符指针是在文字常量区开辟了一段空间存放字符串,故字符指针是只想这个文字常量区的地址。

指针函数

指针函数就是一个返回值为指针的函数,指针函数是指带指针的函数,函数返回类型是某一类型的指针,即本质是一个函数。

函数定义:类型标识符 * 函数名(参数表)

普通的函数定义如下所示:

int  fun(int x,int y);

指针函数定义如下所示:

int* fun(int x,int y);

普通的函数与指针函数只是多了一个 号的区别。上述定义的指针函数其返回值是一个 int 类型的指针,是一个地址,而上述普通函数返回的是一个int值。* 所以指针函数一定有函数返回值**,同时函数返回值必须赋给同类型的指针变量。

示例

#include <stdio.h>
int* fun(int x, int y);//函数申明
int* fun1(int x, int y);//函数申明
int main()
{
    int* p = fun(3, 4);
    int* p1 = fun1(3, 4);
    printf("p的地址为=%p,p所指向的数据是=%d\n",p,*p);
    printf("p1的地址为=%p,p1所指向的数据是=%d\n", p1, *p1);
    return 0;
}
/*实现x+y,同时返回存储的地址*/
int* fun(int x, int y)
{
    static int sum = 0;//静态变量
    int* p = &sum;
    sum = x + y;
    return p;
}
/*实现x+y,同时返回存储的地址*/
int* fun1(int x, int y)
{
    int sum = 0;
    int* p = &sum;
    sum = x + y;
    return p;
}

上面示例定义了fun和fun1函数,同时在函数内用指针p指向了sum变量,但是函数执行完之后会释放函数,虽然最后return返回了该地址的指针,但是由于空间以及释放,故不一定会得到正确的值,需要用static去修饰变量,使得其变为静态变量。静态变量一旦生成,只有在程序结束才会释放,所以指针能一直访问该变量。 同样的使用全局变量也能解决这个问题。

函数指针

函数指针是指带指针的函数,函数指针的本质是一个指针,该指针的地址指向了一个函数,所以它是指向函数的指针。函数指针就是指向代码段中函数入口地址的指针。

函数定义:类型标识符 (*函数名) (参数)

普通的函数定义如下所示:

int  fun(int x,int y);

函数指针声明格式:

int (*fun)(int x,int y);

其中,int 为返回值,(*fun)作为一个整体,代表的是指向该函数的指针,(int x,int y)为形参列表。其中fun被称为函数指针变量 。函数指针本质是一个指针,其指向一个函数。

函数指针与数组类似,在数组中,数组名代表着该数组的首地址,函数也是一样,函数名即是该数组的入口地址,因此,函数名就是该函数的函数指针。

函数指针是需要把一个函数的地址赋值给它,因此,可以采用如下的两种方式:

p=fun;//第一种写法
p=&fun;//第二种写法

示例

#include <stdio.h>
int (*fun)(int, int); // 声明函数指针,指向返回值类型为int,有两个参数类型都是int的函数
//int (*fun)(int a, int b);   //也可以使用这种方式定义函数指针
int sum(int a, int b);
int Difference(int a, int b);

int main()
{
    fun = sum; // 函数指针fun指向求和的函数sum

    int c = (*fun)(1, 2);
    printf("两数之和为=%d\n", c);
    fun = &Difference; // 函数指针fun指向求差值的函数Difference
    c = (*fun)(5, 3);
    printf("两数之差为=%d\n", c);

    return 0;
}
/*求最大值*/
int sum(int a, int b) {
    return a+b;
}

/*求差值*/
int Difference(int a, int b) {
    return a-b;
}

上面示例定义了sum求和函数和Difference求差函数,可见函数指针fun指向函数的时候,可以添加取址符&,也可以不添加,所指向的为函数的入口。

指针函数和函数指针

定义

指针函数本质是一个函数,其返回值为指针。
函数指针本质是一个指针,其指向一个函数。

写法

指针函数:int* fun(int x,int y); 函数指针:int (* fun)(int x,int y);

用途

当项目比较大,代码变得复杂了以后,有许多的函数的返回值,包括函数入参都是相同的,这时候如果要调用不同的排序方法,就可以使用指针函数来实现,我们只需要修改函数指针初始化的地方,而不需要去修改每个调用的地方。

更多回帖

发帖
×
20
完善资料,
赚取积分