0%

C语言指针

RabbitMQ是一个由Erlang语言开发的AMQP的开源实现。AMQP,Advanced Message Queue,高级消息队列协议。它是应用层协议的一个开放标准,为面向消息的中间件设计,基于此协议的客户端与消息中间件可传递消息,并不受产品、开发语言等条件的限制。

指针

一、概念

      指针也就是内存地址,指针变量是用来存放内存地址的变量,不同类型的指针变量所占用的存储单元长度是相同的(跟机器位数,32位则占4字节,64位则占8字节),而存放数据的变量因数据的类型不同,所占用的存储空间长度也不同。指针是一个占据存储空间的实体在这一段空间起始位置的相对距离值。在C/C++语言中,指针一般被认为是指针变量,指针变量的内容存储的是其指向的对象的首地址,指向的对象可以是变量(指针变量也是变量)、数组、函数等占据存储空间的实体。

1
2
3
4
5
6
7
8
9
int p;             // p一个普通的整型变量  
int *p; // p是一个返回整型数据的指针
int p[3]; // p是一个由整型数据组成的数组
int *p[3]; // p是一个由返回整型数据的指针所组成的数组
int (*p)[3]; // p是一个指向由整型数据组成的数组的指针
int **p; // p是一个指针,指向的内容也是一个指针
int p(int); // p是一个函数
int (*p)(int); // p是一个指向有一个整型参数且返回类型为整型的函数的指针
int *(*p(int))[3]; // p是一个参数为一个整数且返回一个指向由整型指针变量组成的数组的指针变量的函数

二、扩展

  1. 易混淆的概念

    • 指针的类型
    • 指针指向的类型
    • 指针的值,也称指针指向的内存区
      • 保存的是一个地址(32位系统下所有类型的指针的值都是一个32位整数)
      • 其含义是指针指向的类型的内存首地址,具体长度要跟指针指向的类型有关系
    • 指针本身所占据的内存区
      • 指针本身占了多大的内存,长度固定,32位系统下就是4字节,64位系统下就是8字节
  2. 指针与取地址

    • vim val.c,代码如下:
1
2
3
4
5
6
7
8
9
10
#include <stdio.h>

void main(void)
{
int num = 10;
int *p = &num;

printf("num的地址是: %d,num的值是: %d\n",&num, num);
printf("p的地址是: %d,p的值是: %d\n", &p, p);
}
  • 编译gcc val.c
  • 运行./a.out,可得到结果如下:
1
2
num的地址是: -347686596,num的值是: 10
p的地址是: -347686608,p的值是: -347686596

&是取地址操作符,当&作用于一个对象上时会返回该对象的地址。

可看到,&num就是取变量num的地址,就是p的内容

  1. 解引用与指针赋值
    • vim val.c,代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
int main(void)
{
int num = 7;
int *p = &num;
printf("数值%d所在的地址是%p\n", num, p);
printf("指针p所指向的地址为%p, 该地址上所保存的值为%d\n", p, *p);

*p = 100;
printf("指针p所指向的地址为%p, 该地址上所保存的值为%d\n", p, num);
return 0;
}
  • 编译运行可得到结果如下:
1
2
3
数值7所在的地址是 0x7ffeef9e1938
指针p所指向的地址为 0x7ffeef9e1938 , 该地址上所保存的值为7
指针p所指向的地址为 0x7ffeef9e1938 , 该地址上所保存的值为100

*是解引用操作符,意为取指针指向的地址的内容

可看到,*p赋值,从而改变p所指的地址上所保存的值,从而改变此地址所存储的变量num的值。

  1. 多级指针
    • vim arr.c,代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
int main(void)
{
int val = 10;
int *p = &val;
int **p2 = &p;
printf("变量val的地址是:%p\n", &val);
printf("变量val的地址是:%p\n", p);

printf("指针变量p的地址是:%p\n", &p);
printf("指针变量p的地址是:%p\n", p2);

printf("变量val的值是:%d\n", val);
printf("变量val的值是:%d\n", *p);
printf("变量val的值是:%d\n", **p2);

return 0;
}
  • 编译运行可得到结果如下:
1
2
3
4
5
6
7
变量val的地址是:0x7ffeef039938
变量val的地址是:0x7ffeef039938
指针变量p的地址是:0x7ffeef039930
指针变量p的地址是:0x7ffeef039930
变量val的值是:10
变量val的值是:10
变量val的值是:10

*操作符在C语言下共有3种用法:

算术表达式中的乘

定义指针

解引用

  1. 引用(C语言没有引用,C++才有)

    • vim ref.c,代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include<stdio.h>

int main()
{
int val = 7, val2 = 999;
int &refval = val, &refval2 = val2; ///引用必须要初始化,使其绑定到一个变量上
///修改引用的值将改变其所绑定的变量的值
refval = -12;
printf("val的值是:%d,引用refval的值是:%d\n", val, refval);

///将引用b赋值给引用a将改变引用a所绑定的变量的值,
///引用一但初始化(绑定),将始终绑定到同一个特定对象上,无法绑定到另一个对象上
refval = refval2;
printf("val的值是:%d,引用refval的值是:%d\n", val, refval);//999
return 0;
}
  • 编译运行g++ ref.c可得到结果如下:
1
2
val的值是:-12,引用refval的值是:-12
val的值是:999,引用refval的值是:999

在C++中,&是引用操作符,作用在引用上的所有操作都会直接改变改引用所绑定的对象

三、延伸

  1. 指针的算术运算
    • 指针的每一次递增,它其实会指向下一个元素的存储单元。
    • 指针的每一次递减,它都会指向前一个元素的存储单元。
    • 指针在递增和递减时跳跃的字节数取决于指针所指向变量数据类型长度,比如int就是4个字节。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <stdio.h>
int main(void)
{
int val = 10;
int *p1 = &val;

printf("val的地址是:%d, %p\n", p1, p1);
printf("val的地址是:%d, %p\n", &val, &val);
printf("val的值是:%d, %d\n", val, *p1);

p1++;

printf("p1自增后的地址是:%d, %p\n", p1, p1);
printf("val的地址是:%d, %p\n", &val, &val);

return 0;
}

编译运行得到结果如下:

val的地址是:-511694536, 0x7ffee1802938
val的地址是:-511694536, 0x7ffee1802938
val的值是:10, 10
p1自增后的地址是:-511694532, 0x7ffee180293c
val的地址是:-511694536, 0x7ffee1802938
  1. 指针数组
  2. 指向指针的指针
  3. 传递指针给函数
  4. 从函数返回指针

TODO

四、参考

  1. 参考一
  2. 参考二
  3. 参考三

函数指针

一、概念

      函数是计算机程序中一段可执行代码的封装,当程序运行时函数会被加载到内存布局中的代码段位置,这段代码会有一段内存空间,有内存空间就会有地址,这段内存空间的首地址就是函数的地址。一个变量的内存首地址可以存储在相应的指针变量中,同样的,函数的首地址也以存储在某个函数指针变量中,此时可以通过这个函数指针变量来调用所指向的函数。

声明方式:typedef int (*fun_ptr)(int,int);,即声明一个指向同样参数、返回值的函数指针类型。

函数指针和函数本身不可重名。

二、使用

  1. 函数名及取函数地址
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>

void (*ptrFunc1)();

void (*ptrFunc2)();

void func() {
printf("hello,func:·\n");
}

int main() {
func();

ptrFunc1 = &func;
ptrFunc2 = func;

printf("%p -- %p\n", func, &func);
printf("%p -- %p\n", ptrFunc1, &ptrFunc1);
printf("%p -- %p\n", ptrFunc2, &ptrFunc2);
}
  • 运行得到结果:

    1
    2
    3
    4
    hello,func:·
    0x10ea01ed0 -- 0x10ea01ed0
    0x10ea01ed0 -- 0x10ea03010
    0x10ea01ed0 -- 0x10ea03018
  • 函数名funcptrFunc1ptrFunc2函数指针都是函数指针

    • func函数名是一个函数指针常量,而ptrFunc1ptrFunc2是函数数指针变量。
  • 取地址运算法&要求其操作对象是一个对象,但函数名不是对象(函数本身是一个对象),按照要求&func是非法的,但编译器允许这么做,c/c++标准的制定者也承认了其合法性。

  • func是函数的首地址,而&func表示一个指向函数func这个对象的地址。

  1. 多种用法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void (*funP)(int);

void (*funA)(int);

void myFun(int x);

int main() {
printf("默认调用,");
myFun(100);
printf("*myFun调用,");
(*myFun)(100);
printf("&myFun调用,");
(&myFun)(100); // 等价于funP(100)

printf("funP调用,");
funP = &myFun;
(*funP)(200);
printf("*funP调用,");
funP(200);

printf("funA调用,");
funA = myFun;
funA(300);
printf("*funA调用,");
(*funA)(300);

return 0;
}

void myFun(int x) {
printf("myFun: %d\n",x);
}
  • 运行得到结果:

    1
    2
    3
    4
    5
    6
    7
    默认调用,myFun: 100
    *myFun调用,myFun: 100
    &myFun调用,myFun: 100
    funP调用,myFun: 200
    *funP调用,myFun: 200
    funA调用,myFun: 300
    *funA调用,myFun: 300
  • myFun的函数名与funPfunA函数指针一样,都是函数指针。

    • myFun函数名是一个函数指针常量
    • funPfunA是函数指针变量
  • 函数名调用如果都得如(*myFun)(10)这样,书写与读起来都是不方便和不习惯,所以C语言的设计者们才会设计成又可允许myFun(10)这种形式地调用。

    • 为了统一调用方式,funP函数指针变量也可以funP(10)的形式来调用。
  • 赋值时,可以写成funP=&myFun形式,也可以写成funP=myFun

  • 声明时,void myFun(int)不能写成void (*myFun)(int)形式,void (*funP)(int)也不能写成void funP(int)形式。

  1. 函数指针常量与函数指针变量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdio.h>
#include <stdlib.h>

void (*funP)(int);
void (*funA)(int);
void myFun(int x);
int main() {
funP = &myFun;
funA = myFun;

printf("sizeof(myFun)=%d\n", sizeof(myFun));
printf("sizeof(funP)=%d\n", sizeof(funP));
printf("sizeof(funA)=%d\n", sizeof(funA));

printf("myFun:%p, &myFun:%p\n", myFun, &myFun);
printf("funP:%p, &funP:0x%p\n", funP, &funP);
printf("funA:%p, &funA:%p\n", funA, &funA);
return 0;
}

void myFun(int x) {
printf("myFun: %d\n",x);
}
  • 运行得到结果:

    1
    2
    3
    4
    5
    6
    sizeof(myFun)=1
    sizeof(funP)=8
    sizeof(funA)=8
    myFun:0x108f23ef0, &myFun:0x108f23ef0
    funP:0x108f23ef0, &funP:0x0x108f25018
    funA:0x108f23ef0, &funA:0x108f25010
  • 函数指针变量跟普通的指针一样在64位系统下大小都为4,函数指针常量的大小为1。

  • 函数指针变量和函数指针常量存储在内存的不同位置。

  1. 函数指针作为参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <stdio.h>
#include <stdlib.h>

typedef void(*FunType)(int); //前面加一个typedef关键字,这样就定义一个名为FunType函数指针类型,而不是一个FunType变量,/形式同typedef int* PINT;

void myFun(int x);
void hisFun(int x);
void herFun(int x);
void callFun(FunType fp,int x);

int main() {
callFun(myFun,100);//传入函数指针常量,作为回调函数
callFun(hisFun,200);
callFun(herFun,300);

return 0;
}

void callFun(FunType fp, int x) {
fp(x);//通过fp的指针执行传递进来的函数,注意fp所指的函数有一个参数
}

void myFun(int x) {
printf("myFun: %d\n",x);
}

void hisFun(int x) {
printf("hisFun: %d\n",x);
}

void herFun(int x) {
printf("herFun: %d\n",x);
}
  • 运行得到结果:

    1
    2
    3
    myFun: 100
    hisFun: 200
    herFun: 300

三、参考

  1. 参考一
  2. 参考二