7月27日笔记C语言基础指针0

内存地址

  • 字节:字节是内存的容量单位,英文称为 byte,一个字节有8位,即 1byte = 8bits
  • 地址:系统为了便于区分每一个字节而对它们逐一进行的编号,称为内存地址,简称地址。

基地址

  • 单字节数据:对于单字节数据而言,其地址就是其字节编号。
  • 多字节数据:对于多字节数据而言,地址是其所有字节中编号最小的那个,称为基地址。

取址符

  • 每个变量都是一块内存,都可以通过取址符 & 获取其地址  %p
  • 例如:

int a = 100;

printf("整型变量 a 的地址是: %p\n", &a);

char c = 'x';

printf("字符变量 c 的地址是: %p\n", &c);

double f = 3.14;

printf("浮点变量 f 的地址是: %p\n", &f);

  • 注意:
    • 虽然不同的变量的尺寸是不同的,但是他们的地址的尺寸确实一样的。
    • 不同的地址虽然形式上看起来是一样的,但由于他们代表的内存尺寸和类型都不同,因此它们在逻辑上是严格区分的。

指针基础

  • 指针的概念:
    • 地址。比如 &a 是一个地址,也是一个指针,&a 指向变量 a。
    • 专门用于存储地址的变量,又称指针变量。
  • 指针的定义:

int    *p1; // 用于存储 int  型数据的地址,p1 被称为 int  型指针,或称整型指针

char   *p2; // 用于存储 char 型数据的地址,p2 被称为 char 型指针,或称字符指针

double *p3; // 用于存储double型数据的地址,p3 被称为 double 型指针

  • 指针的赋值:赋给指针的地址,类型需跟指针的类型相匹配。

int a = 100;

p1 = &a; // 将一个整型地址,赋值给整型指针p1

char c = 'x';

p2 = &c; // 将一个字符地址,赋值给字符指针p2

double f = 3.14;

p3 = &f; // 将一个浮点地址,赋值给浮点指针p3

  • 指针的索引:通过指针,取得其指向的目标

*p1 = 200; // 将 p1 指向的目标(即a)修改为200,等价于 a = 200;

*p2 = 'y'; // 将 p2 指向的目标(即c)修改为'y',等价于 c = 'y';

*p3 = 6.6; // 将 p3 指向的目标(即f)修改为6.6,等价于 f = 6.6;

  • 指针的尺寸
    • 指针尺寸指的是指针所占内存的字节数
    • 指针所占内存,取决于地址的长度,而地址的长度则取决于系统寻址范围,即字长
    • 结论:指针尺寸只跟系统的字长有关,跟具体的指针的类型无关

void型指针

  • 概念:无法明确指针所指向的数据类型时,可以将指针定义为 void 型指针
  • 要点:
    1. void 型指针无法直接索引目标,必须将其转换为一种具体类型的指针方可索引目标
    2. void 型指针无法进行加减法运算
  • void关键字的三个作用:
    1. 修饰指针,表示指针指向一个类型未知的数据。
    2. 修饰函数参数列表,表示函数不接收任何参数。
    3. 修饰函数返回类型,表示函数不返回任何数据。
  • 示例:

// 指针 p 指向一块 4 字节的内存,且这4字节数据类型未确定

void *p = malloc(4);

// 1,将这 4 字节内存用来存储 int 型数据

*(int *)p = 100;

printf("%d\n", *(int *)p);

// 2,将这 4 字节内存用来存储 float 型数据

*(float *)p = 3.14;

printf("%f\n", *(float *)p);


野指针

  • 概念:指向一块未知区域的指针,被称为野指针。野指针是危险的。
  • 危害:
    1. 引用野指针,相当于访问了非法的内存,常常会导致段错误(segmentation fault)
    2. 引用野指针,可能会破坏系统的关键数据,导致系统崩溃等严重后果
  • 产生原因:
    1. 指针定义之后,未初始化
    2. 指针所指向的内存,被系统回收
    3. 指针越界
  • 如何防止:
    1. 指针定义时,及时初始化
    2. 绝不引用已被系统回收的内存
    3. 确认所申请的内存边界,谨防越界

空指针

        很多情况下,我们不可避免地会遇到野指针,比如刚定义的指针无法立即为其分配一块恰当的内存,又或者指针所指向的内存被释放了等等。一般的做法就是将这些危险的野指针指向一块确定的内存,比如零地址内存。

  • 概念:空指针即保存了零地址的指针,亦即指向零地址的指针。
  • 示例:

// 1,刚定义的指针,让其指向零地址以确保安全:

char *p1 = NULL;

int  *p2 = NULL;

// 2,被释放了内存的指针,让其指向零地址以确保安全:

char *p3 = malloc(100); // a. 让 p3 指向一块大小为100个字节的内存

free(p3);               // b. 释放这块内存,此时 p3 相当于指向了一块非法内存

p3 = NULL;              // c. 让 p3 指向零地址


指针运算

  • 指针加法意味着地址向上移动若干个目标
  • 指针减法意味着地址向下移动若干个目标
  • 示例:

int  a = 100;

int *p = &a; // 指针 p 指向整型变量 a

int *k1 = p + 2; // 向上移动 2 个目标(2个int型数据)

int *k2 = p - 3; // 向下移动 3 个目标(3个int型数据)


数组名涵义

  • 数组名有两个含义:
    • 第一含义是:整个数组
    • 第二含义是:首元素地址
  • 当出现以下情形时,那么数组名就代表整个数组:
    • 在数组定义中
    • 在 sizeof 运算表达式中
    • 在取址符&中
  • 其他任何情形下,那么数组名就代表首元素地址。即:此时数组名就是一个指向首元素的指针。
  • 示例:

int a[3];                  // 此处,a 代表整个数组

printf("%d\n", sizeof(a)); // 此处,a 代表整个数组

printf("%p\n", &a);        // 此处,a 代表整个数组,此处为整个数组的地址

int *p = a;       // 此处,a 代表首元素 a[0] 的地址,等价于 &a[0]

p = a + 1;        // 此处,a 代表首元素 a[0] 的地址,等价于 &a[0]

function(a);      // 此处,a 代表首元素 a[0] 的地址,等价于 &a[0]

scanf("%d\n", a); // 此处,a 代表首元素 a[0] 的地址,等价于 &a[0]

        C语言只有在第一含义的场合下表现为数组,其他大部分场合都表现为首元素的地址,当数组表现为首元素地址时,实际上就是一个指向其首元素的指针。数组运算实际上就是指针运算。


数组下标

  • 数组下标实际上是编译系统的一种简写,其等价形式是:

                        a[i] = 100;  等价于  *(a+i) = 100;

  • 根据加法交换律,以下的所有的语句均是等价的:

  a[i] = 100;

*(a+i) = 100;

*(i+a) = 100;

  i[a] = 100;

  • 数组运算,等价于指针运算。

字符串常量

  • 字符串常量在内存中的存储,实质是一个匿名数组
  • 匿名数组,同样满足数组两种涵义的规定
  • 示例:

printf("%d\n", sizeof("abcd")); // 此处 "abcd" 代表整个数组

printf("%p\n", &"abcd");        // 此处 "abcd" 代表整个数组

printf("%c\n", "abcd"[1]); // 此处 "abcd" 代表匿名数组的首元素地址

char *p1 = "abcd";         // 此处 "abcd" 代表匿名数组的首元素地址

char *p2 = "abcd" + 1;     // 此处 "abcd" 代表匿名数组的首元素地址


char型指针

        char型指针实质上跟别的类型的指针并无本质区别,但由于C语言中的字符串以字符数组的方式存储,而数组在大多数场合又会表现为指针,因此字符串在绝大多数场合就表现为char型指针。

  • 定义:

                char *p = "abcd";


多级指针

  • 如果一个指针变量 p1 存储的地址,是另一个普通变量 a 的地址,那么称 p1 为一级指针
  • 如果一个指针变量 p2 存储的地址,是指针变量 p1 的地址,那么称 p2 为二级指针
  • 如果一个指针变量 p3 存储的地址,是指针变量 p2 的地址,那么称 p3 为三级指针
  • 以此类推,p2、p3等指针被称为多级指针
  • 示例:

int a = 100;

int   *p1 = &a;  // 一级指针,指向普通变量

int  **p2 = &p1; // 二级指针,指向一级指针

int ***p3 = &p2; // 三级指针,指向二级指针


指针万能拆解法

  • 任意的指针,不管有多复杂,其定义都由两部分组成。
    • 第1部分:指针所指向的数据类型,可以是任意的类型
    • 第2部分:指针的名字

  • 示例:

char   (*p1);      // 第2部分:*p1; 第1部分:char;

char  *(*p2);      // 第2部分:*p2; 第1部分:char *;

char **(*p3);      // 第2部分:*p3; 第1部分:char **;

char   (*p4)[3];  // 第2部分:*p4; 第1部分:char [3];

char   (*p5)(int, float); // 第2部分:*p5; 第1部分:char (int, float);

  • 注解:
  1. 上述示例中,p1、p2、p3、p4、p5本质上并无区别,它们均是指针
  2. 上述示例中,p1、p2、p3、p4、p5唯一的不同,是它们所指向的数据类型不同
  3. 第1部分的声明语句,如果由多个单词组成,C语言规定需要将其拆散写到第2部分的两边

const 型指针

  • const型指针有两种形式:常指针 常目标指针
  1. 常指针:const修饰指针本身,表示指针变量本身无法修改。
  2. 常目标指针:const修饰指针的目标,表示无法通过该指针修改其目标。

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

int a = 100;

int b = 200;

// 第1中形式,const修饰p1本身,导致p1本身无法修改

int * const p1 = &a;

// 第2中形式,const修饰p2的目标,导致无法通过p2修改a

int const *p2 = &a;

const int *p2 = &a;


函数指针

  • 概念:指向函数的指针,称为函数指针。
  • 特点:函数指针跟普通指针本质上并无区别,只是在取址和索引时,取址符和星号均可省略
  • 示例:

void   f (int); // 函数 f 的类型是: void (int)

void (*p)(int); // 指针 p 专门用于指向类型为 void (int) 的函数

p = &f; // p 指向 f(取址符&可以省略)

p =  f; // p 指向 f

// 以下三个式子是等价的:

  f (666); // 直接调用函数 f

(*p)(666); // 通过索引指针 p 的目标,间接调用函数 f

 p (666); // 函数指针在索引其目标时,星号可以省略

  • 要点:
    1. 函数指针是一类专门用来指向某种类型函数的指针。
    2. 函数的类型不同,所需要的函数指针也不同。
    3. 函数的类型,与普通变量的类型判定一致,即去除声明语句中的标识符之后所剩的语句。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值