浅谈C语言指针

1 指针

指针是什么?与内存地址的关系是什么?

下图是 4G 内存中每个字节单元和它的编号:

  • 每个字节单元可以存储8个二进制位,即1个字节大小的内容;
  • 字节单元也被称为内存单元;
  • 为了方便寻址,每个内存单元都有唯一的编号,这个编号就是内存地址(Address)或者指针(Address)
  • 地址从 0 开始依次增加,对于 32 位环境,程序能够使用的内存为 4GB,最小的地址为 0x00000000,最大的地址为 0xFFFFFFFF

【答】

  • 指针是内存单元的编号;
  • 指针变量的值是一个内存地址。用*取指针变量的值得到的就是该指针变量指向的内存地址存储的值。

1.1 如何创建指针

#include <stdio.h>

int main() {
  int a = 10;
  int* p1 = &a;
  int *p2 = &a;
  int** pp = &p1;

  printf("%p\n", &a);
  printf("%p\n", p1);
  printf("%p\n", p2);
  printf("%p\n", pp);

  system("pause");
  return 0;
}

一次执行中,运行结果如下:

我们画图来表示:

  • 但在编写代码的过程中,我们认为变量名表示的是数据本身;例如,a 是 int 型,表示的是 4字节的整数。

  • a 是一个变量,用来存放整数,需要在前面加&来获得它的地址;&a 表示就是该变量的首地址。

  • 指针可以认为是一种数据类型,int*就可以视为一个整体。p1 就是一个指针,这个指针指向变量 a 的首地址。

  • int* p = &a;int *p = &a; 写法略有差异,但是实际功能是相同的。因此, &a,p1,p2 表示的是同一个内存单元。

  • int** pp 就等同于 (int*)* pp,pp 是指针,指向一个内存单元,在32位系统中,int*占4个字节,所以 pp 存储的值等于 0x0133F7D4,这个值又是一个内存地址,或者说指针。

【答】:定义指针用 int* p; 或者 int *p,个人更推荐前者。

1.2 指针占多少字节

我们先来看一个程序:

#include <stdio.h>

int main() {
  char* p1;
  short* p2;
  int* p3;
  long* p4;
  float* p5;
  double* p6;

  printf("%d\n", sizeof(p1));
  printf("%d\n", sizeof(p2));
  printf("%d\n", sizeof(p3));
  printf("%d\n", sizeof(p4));
  printf("%d\n", sizeof(p5));
  printf("%d\n", sizeof(p6));	 
  system("pause");
  return 0;
}

我们来看一下运行结果:

  • 我们发现任何类型的指针变量都是占用4个字节。
  • 因为我们的程序是用32位编译的,所以总是4个字节

【答】
指针的大小是固定的;指针所占的字节数跟编译的程序位数有关。如果是32位程序,指针的宽度就是4字节,如果是64位程序,指针的宽度就是8字节。

1.3 指针变量的运算

我们再来看一段程序:

#include <stdio.h>

int main()
{
  char c = 'C';
  short s = 1;
  int i = 100;
  double d = 99.9;
  char* p1 = &c;
  short* p2 = &s;
  int* p3 = &i;
  double* p4 = &d;
  printf("size char=%6d short=%5d int=%7d double=%4d\n", sizeof(char), sizeof(short), sizeof(int), sizeof(double));
  printf("Init p1=%8p p2=%8p p3=%8p p4=%8p\n", p1, p2, p3, p4);
  // 加法运算
  p1++;p2++;p3++;p4++;
  printf("p++  p1=%8p p2=%8p p3=%8p p4=%8p\n", p1, p2, p3, p4);
  // 减法运算
  p1-=2;p2-=2;p3-=2;p4-=2;
  printf("p-=2 p1=%8p p2=%8p p3=%8p p4=%8p\n", p1, p2, p3, p4);

  system("pause");
  return 0;
}

执行结果如下:

  • 自增:p1,p2,p3,p4每次加1,他们的地址分别增加1、2、4、8个字节,这个增量正好等于 char,short,int,double 类型的长度。
  • 减法:p1,p2,p3,p4每次减2,他们的地址分别减少2、4、8、16个字节,这个减少量正好等于 char,short,int,double 类型的长度的2倍。

1.4 单目操作符&*

首先,定义指针变量void* p 或者 void *p,以及定义引用变量 int& a 或者 int &a;在定义变量时,会比 &* 单纯作为单目运算符要多一个类型声明。

首先,作为单目运算符,它的特点就是只有一个操作数:

int a = 1;
int* p = &a;
*p = 10;
  • 首先定义了一个变量a
  • 接着,定义了指针变量p,并为它赋值。赋值时,用上了单目运算符&来取得变量a的首地址,并赋值给指针p。
  • 最后,再次使用了单目运算符*来获取指针p指向的数据,并修改了数据内容。

& 作为 单目运算符 使用时,是取地址符
* 作为 单目运算符 使用时,是解引用符

2 指针常量和常量指针

我之前在学习指针的时候,总是会把指针常量和常量指针混淆起来。最主要的原因是,按照一般的习惯,我们是从左向右读,所以我们会理所当然地认为const int* p常量指针。但是实际上你倒过来念就是正确的。

2.1 指针常量

int a = 0;
int b = 1;

// 指针常量
const int* p = &a;

// *p = 10; // 错误,内容不可以修改
p = &b;     // 正确,指针可以修改

记忆方法:按照英文反着念 int*,const: 指针常量。中文的习惯,最终定性为“常量”,所以指针指向的数据是个常量,不可以被修改。

2.2 常量指针

int a = 0;
int b = 1;

// 常量指针
int* const p = &a;

*p = 10;   // 正确,指针指向的数据可以修改
// p = &b; // 错误,指针地址不可以修改

记忆方法:反着念定义关键字 const,int*:常量指针。中文的习惯,最终定性为“指针”,常量修饰“指针”一词,所以指针地址不可以被修改。但是,指针指向的数据可以被修改。

2.3 地址和数据都不许改

int a = 0;
int b = 1;

const int* const p = &a;

// *p = 10;  // 错误,指针指向的数据不可以修改
// p = &b;   // 错误,指针地址不可以修改

3 指针与数组

3.1 访问数组中的元素

int arr[] = {1,2,3,4,5};
int* start = arr;
  • 首先,初始化了整型数组 arr;
  • 接着,把数组名赋值给了指针start;

函数名、字符串名和数组名表示的是代码块或数据块的首地址

我们想要遍历数组,首先需要计算数组的元素个数:

int len = sizeof(arr) / 4;

然后,不使用指针时,我们访问数组的是这样:

for (int i = 0; i < len; i++) {
  printf("%d\n", arr[i]);
}

使用指针访问数组是这样的:

for (int i = 0; i < len; i++) {
  printf("%d\n", *(start+i));
}

3.2 通过char型指针访问short数组会发生什么?

short arr[] = {1, 2, 3};
char* p = reinterpret_cast<char *>(arr);

for (int i = 0; i < 3; i++) {
  printf("%d\n", *(p+i));
}

运行结果:

4 函数指针与指针函数

4.1 函数指针

函数指针,其本质是一个指针变量,该指针指向某个函数。

#include <stdio.h>
void sayHello();

int main()
{	
  void (*p)();
  p = sayHello;
  (*p)(); // 效果等同于调用 sayHello();
  p();    // 效果等同于调用 sayHello();

  system("pause");
  return 0;
}

void sayHello() {
  printf("Hello World!");
}

利用函数指针进行调用:

  • (*p)()
  • p()
    很久很久以前C语言只允许前者,后来大家觉得这么写太麻烦就规定了后者能达到同样效果。
    后者在编译时和前者做相同的事情。

类似的语法上的便利还有:

  • p->h 通过指针访问结构成员,等价于 (*p).h
  • p[n] 通过指针访问数组元素,等价于 *(p+n)

4.2 指针函数

指针函数,简单的来说,就是一个返回指针的函数,其本质是一个函数,而该函数的返回值是一个指针。形如

int *fun(int x,int y);

4.3 typedef函数指针

改造一下4.1函数指针中的例子:

void sayHello();

// typedef 函数指针
typedef void (*HI)();
HI funHi;

int main()
{
  funHi = sayHello;
  funHi();    // 效果等同于调用 sayHello();
  (*funHi)(); // 效果等同于调用 sayHello();
  return 0;
}

void sayHello() {
  printf("Hello World!");
}

参考文档

《C语言指针是什么?1分钟彻底理解C语言指针的概念》阅读

《函数名与函数指针(了解)》阅读

《typedef函数指针用法》阅读

posted @ 2021-06-11 16:35  极客子羽  阅读(223)  评论(0编辑  收藏  举报