跳到主要内容

C 指针基础

1. 基本概念

内存地址

编译器为了知道具体每一个变量名对应的存放地址,所以当你读取某一个变量时,编译器就会找到变量名所在的地址,并且根据变量的类型读取相应地址范围的数据。

  • 字节:字节是内存的容量单位,英文称为 byte,一个字节有8位,即 1byte = 8bit

  • 地址:系统为了便于区分每一个字节,会对地址进行逐一编号,称为内存地址,简称叫做地址。

  • 基地址

    • 对于单字节数据而言,基地址就是其所在内存的字节编号。
    • 对于多字节数据而言,基地址就是其所在的字节编码最小的那个。

2. 指针和指针变量

指针也就是内存地址,指针变量是用来存放内存地址的变量。指针变量也有类型,就像其他变量和常量一样,必须在使用指针存储其他变量地址之前,对其进行声明。指针变量声明的一般形式为:

type *var_name
  • 定义指针变量
int    *ip;    /* 一个整型的指针 */
double *dp; /* 一个 double 型的指针 */
float *fp; /* 一个浮点型的指针 */
char *ch; /* 一个字符型的指针 */

所有实际数据类型,不管是整型、浮点型、字符型,还是其他的数据类型,对应指针的值的类型都是一样的,都是一个代表内存地址的长的十六进制数。

不同数据类型的指针之间唯一的不同是,指针所指向的变量或常量的数据类型不同。

3. 取地址运算符和取值运算符

  • 如果需要获取某个变量的地址,可以使用取址运算符(&)。
int *pa = &a;
char *pb = &b;
float *pc = &c;
  • 如果需要访问指针变量指向的数据,可以使用取值运算符(*)。
  • (*)结引用就是去访问指针变量所存储的起始位置的这片内存。
printf("%d, %c, %.2f", *pa, *pb, *pc);

4. 如何去使用指针

  • 定义指针变量。
  • 把变量地址赋值给指针。
  • 访问指针变量中可用的地址。

示例:

#include <stdio.h>

int main(int argc, char const *argv[])
{
int var = 10; /* 实际变量的声明 */
int *ip; /* 指针变量的声明 */

ip = &var; /* 在指针变量中存储 var 的地址 */

printf("var 变量的地址: %p\n", &var);
printf("ip 变量存储的地址: %p\n", ip);
printf("*ip 变量的值: %d\n", *ip);

return 0;
}

5. 特殊指针

5.1 野指针

int *p;
*p = 20; // 这样定义和去使用是错误的
  • 野指针的危害是非常大的,因为我们不知道一开始指针指向地方在哪里,有可能是在我们程序空间执行的任意位置。

  • 为了避免野指针的出现,定义一个指针的时候一定要进行初始化,要么向 malloc 申请存放空间,要么给一个布局变量的地址,要么给 **NULL **。

int *p = malloc(sizeof(int)); // malloc 申请空间
*p = 10;

int *p = 10; // 指针初始化赋值
int *p = NULL; // 指针初始化赋空值
  • free(p) 可以释放申请的空间,但是释放之后的 p 不能再去使用。

5.2 空指针(NULL)

  • 在指针变量声明的时候,如果没有明确的地址访问,为指针赋一个 NULL 是良好的编程习惯。

  • 赋为 NULL 值的指针被叫做空指针,它指向地址的不可访问区,空指针一般用来避免野指针的产生。

#include <stdio.h>

int main(int argc, char const *argv[])
{
int *ptr = NULL; // 指针初始化附空值

printf("ptr 的地址是 %p\n", ptr);

return 0;
}

5.3 0 指针(零指针)

  • 地址里面最小的地址 0。
int *p = 0;          // 可以理解成给指针赋值为 0 指针

5.4 void *

泛型指针,通用指针,万能指针

#include <stdio.h>

int main(int argc, char const *argv[])
{
void *p = malloc(16);

int *p1 = p;
float *p2 = p;
double *p3 = p;
char *p4 = p;

printf("p = %p, p1 = %p\n, p2 = %p\n, p3 = %p\n, p4 = %p\n", p, p1, p2, p3, p4);

return 0;
}
  • 不确定后面具体要使用什么类型指针时可以使用**void * **。
  • 可以赋值给任意类型的指针。
  • 没有确定类型时,不能使用。

5.5 const 指针

const 指针有两种形式:①常指针 ②常目标指针

  • 常指针:const 修饰指针变身,表示指针变量本身无法修改。

ZYFvnz.png

  • 常目标指针:const 修饰指针的目标,表示无法通过该指针修改其目标。

ZYFw7A.png

常指针在实际应用中不常见,常目标指针在实际应用中广泛可见,用来限制指针的读写权限。

6. 指针的算术运算

指针是一个用数值表示的地址。因此,可以对指针进行4种算术运算:++、--、+、- 。

  • 指针的每一次递增,它其实会指向下一个元素的存储单元。
  • 指针的每一次递减,它都会指向前一个元素的存储单元。
  • 指针的递增和递减时跳跃的字节数取决于指针所指向变量数据类型长度,比如int就是4个字节。

6.1 指针的递增

指针可以代替数组,因为指针变量可以递增,而数组不能递增,数组可以看成是一个指针常量。

#include <stdio.h>

const int MAX = 3;

int main(int argc, char const *argv[])
{
int var[] = {10, 100, 200};
int i, *ptr;

ptr = var; // 指针中的数组地址
for (i = 0; i < MAX; i++)
{
printf("存储地址: var[%d] = %p\n", i, ptr);
printf("存储值: var[%d] = %d\n", i, *ptr);

ptr++; // 指向下一个地址
}
return 0;
}

6.1 指针的递减

#include <stdio.h>

const int MAX = 3;

int main(int argc, char const *argv[])
{
int var[] = {10, 100, 200};
int i, *ptr;

ptr = &var[MAX-1]; // 指针中最后一个元素地址
for (i = MAX; i > 0; i--)
{
printf("存储地址: var[%d] = %p\n", i-1, ptr);
printf("存储值: var[%d] = %d\n", i-1, *ptr);

ptr--; // 指向下一个位置
}
return 0;
}

6.2 指针的比较

指针可以用关系运算符进行比较:==、< 和 >。如果 p1 和 p2 指向两个相关的变量,比如同一个数组中的不同元素,则可以对 p1 和 p2 进行大小比较。

#include <stdio.h>

const int MAX = 3;

int main(int argc, char const *argv[])
{
int var[] = {10, 100, 200};
int i, *ptr;

ptr = var; // 指针中第一个元素地址
i = 0;
while (ptr <= &var[MAX - 1])
{
printf("存储地址: var[%d] = %p\n", i, ptr);
printf("存储值: var[%d] = %d\n", i, *ptr);

ptr++; // 指向上一个位置
i++;
}
return 0;
}