一、认识内存
无论是前面学习的变量还是后面我们要学习的数组,都是逻辑上得表现,最终都要保存到内存中,而内存是线性的,这是物理基础。
内存是以字节为单位进行编址的,内存中的每个字节都对应一个地址,通过地址才能找到每个字节。
1.1 变量的地址
变量对应内存中的一段存储空间,该段存储空间占用一定的字节数,可能是 1个字节,也可能是 4 或是 8 个字节,用这段存储空间的第一个字节的地址表示变量的地址,即低位字节的地址。
变量的地址,可以通过 Reference (&) 引用运算符取得,在此可以称为取地址运算符。
#include <stdio.h> int main() { int a; int b; // 这段代码验证了上面我们说的 内存地址是连续的 // 还验证了第一个字节的地址表示变量的地址,即低位字节的地址。 printf("a = %p\n", &a); printf("b = %p\n", &b); return 0; }
1.2 地址的大小
#include <stdio.h> int main() { char a; short b; int c; long d; float e; double f; printf("&a = %p\n", &a); printf("&b = %p\n", &b); printf("&c = %p\n", &c); printf("&d = %p\n", &d); printf("&e = %p\n", &e); printf("&f = %p\n", &f); //求地址的大小 printf("sizeof(&a) = %d\n", sizeof(&a)); printf("sizeof(&b) = %d\n", sizeof(&b)); printf("sizeof(&c) = %d\n", sizeof(&c)); printf("sizeof(&d) = %d\n", sizeof(&d)); printf("sizeof(&e) = %d\n", sizeof(&e)); printf("sizeof(&f) = %d\n", sizeof(&f)); return 0; }
通过运算的方式,我们可以求得变量的地址。32 位机的情况下,无论是什么类型大小均是 4。而 64 位机大小均是 8。这是由当前机型的地址总线决定的。
1.3 间接访问地址
我们拿到的变量的地址,其实,就是指针了。除了变量,我们还可以通过指针的方式间接的访问内存。
dereference (*) 解引用运算符,在此处我们可以称为,取内容运算符。用法如下:
#include <stdio.h> int main() { char a = 1; short b = 2; int c = 3; long d = 4; float e = 5; double f = 6; printf("a = %d\n", *(&a)); printf("b = %d\n", *(&b)); printf("c = %d\n", *(&c)); printf("d = %lu\n", *(&d)); printf("e = %lf\n", *(&e)); printf("f = %lf\n", *(&f)); }
二、 指针常量(推导-了解即可)
2.1指针是有类型地址常量
#include <stdio.h> int main(void) { char a = 1; short b = 2; int c = 3; long d = 4; float e = 1.2; double f = 2.3; printf("&a = %p\n", &a); printf("&b = %p\n", &b); printf("&c = %p\n", &c); printf("&d = %p\n", &d); printf("&e = %p\n", &e); printf("&f = %p\n", &f); printf("--------------\n"); char* p = (char*)&a; int* pc = (int*)&c; // char* ptr = (char*)0x7ff7b8b463af; printf("a = %d\n", *p); printf("c = %d\n", *pc); // printf("a = %d\n", *((char*)0x7ff7b8b463af)); }
printf("a = %d\n", *p); 等价于 printf("a = %d\n", *((char*)0x7ff7b8b463af));
&a 进行取地址,取出来的地址是有类型的,这个信息对我们来说很重要的。通过上面我们的分析,指针其实就是了一个有类型的 4/8 字节整型常量。
指针的类型,决定了,该指针的寻址能力。即从指针所代表的地址处的寻址范围。
三、指针变量
严格地说,一个指针是一个有类型地址,是一个有类型的常量。用以存放指针的量,我们叫作,指针变量。一个指针变量却可以被赋予不同的指针值,可以能过指针变量改变指向和间接操作。
严格意义上来说,指针指的指针常量,而我们通常意义上所说的指针,多指的指针变量。
3.1 定义
指针的定义:
3.2 释义
-
(*)表示该变量是一个指针变量。
-
type 表示该变量的内存放的地址的寻址能力。
#include <stdio.h> int main() { char c = 1; int i = 30; // incompatible pointer types initializing 'int *' with an expression of type 'char *' [-Wincompatible-pointer-types] int* pc = &c; int* pi = &i; printf("c = %d\n", *pc); printf("i = %d\n", *pi); return 0; }
3.3 指针大小
#include <stdio.h> int main() { char* a; short* b; int* c; float* d; double* e; printf("sizeof(a) = %d\n", sizeof(a)); printf("sizeof(b) = %d\n", sizeof(b)); printf("sizeof(c) = %d\n", sizeof(c)); printf("sizeof(d) = %d\n", sizeof(d)); printf("sizeof(e) = %d\n", sizeof(e)); return 0; }
根据平台不同,32位为4字节,64位为8字节。
3.4 初始化和间接访问
凡是一个有类型的地址,都是可赋给同类型的指针变量。
但是如果直接赋给指针变量一个地址,对其访问是很危险的。因为,我们不知道此址处是否一块什么区域 ,有可能是一段内核区域 ,访问很可能会导致系统崩溃。
所以通常作法是,把一个己经开辟空间的变量的地址赋给指针变量。
#include <stdio.h> int main() { int a = 100; int* pa = &a; printf("*pa = %d\n", *pa); *pa = 200; printf("*pa = %d\n", *pa); return 0; }
3.5 指向、被指向、更改指向
我们常说的,谁指向了谁,就是一种描述指针的指向关系。指向谁,即保存了谁的地址。
#include <stdio.h> int main() { int x = 100; int* ptr = &x; printf("x = %d ptr = %d\n", x, *ptr); int y = 200; ptr = &y; printf("y = %d y = %d ptr = %d\n", y, *ptr); return 0; }
3.6 指针的值,指向地址和本身地址
#include <stdio.h> int main() { int var = 10; int* ptr_var; ptr_var = &var; printf(" var 的值是: %d\n", var); printf("var 的内存地址是: %#x\n", &var); //指针变量 ptr_var 的地址。&ptr_var 返回指针变量 ptr_var 的地址,即指针变量自己的地址。 printf("指针 ptr_var 的地址是: %#x\n", &ptr_var); printf("var 的值是: %d\n", *ptr_var); //指针变量 ptr_var 所存储的地址 即指向 var 的地址 printf("var 的地址是: %#x\n", ptr_var); return 0; }
3.7 野指针
3.71 野指针
一个指针变量,如果,指向一段无效的空间,则该指针称为野指针,是由 invalid pointer 翻译过来,直译是无效指针。常见情型有两种,一种是未初化的指针,一种是指向一种己经被释放的空间。
对野指针的读或写崩溃尚可忍受,对野指针的写入成功,造成的后果是不可估量的。对野指针的读写操作,是危险而且是没有意义的。世上十之八九最难调的 bug皆跟它有关系。
3.7.2 NULL 指针(零值无类型指针)
如何避免野指针呢,色即空。
NULL 是一个宏,俗称空指针,他等价于指针(void)0。(void) 0 是一个很特别的指针,因为他是一个计算机黑洞,既读不出东西,也不写进东西去。所以被赋值 NULL的指针变量,进行读写操作,是不会有内存数据损坏的。
c 标准中是这样定义的:define NULL ((void *)0)
故常用 NULL 来给临时不需要初初始化的指针变量来进行初始化。或对己经被释放指向内存空间的指针赋值。
可以理解为 C 专门拿出了 NULL(零值无类型指针),用于作标志位使用。
3.73 void本质
void 即无类型,可以赋给任意类型的指针,本质即代表内存的最小单位,在 32位机上地位等同于 char。
四、函数指针
1.1 函数名
-
一个函数在编译时被分配一个入口地址,这个地址就称为函数的指针,函数名代表函数的入口地址
1.2 函数指针
-
函数指针:它是指针,指向函数的指针
-
语法格式:
返回值 (*函数指针变量)(形参列表);
-
-
函数指针变量的定义,其中返回值、形参列表需要和指向的函数匹配
-
#include <stdio.h> int func(int a, int b) { return a + b; } int main() { // 函数在编译的时候 会有一个入口地址 // 这个地址就是函数指针地址 // 一个函数在编译时被分配一个入口地址,这个地址就称为函数的指针,函数名代表函数的入口地址 // printf("func = %#x\n", &func); // printf("func = %#x\n", *func); // printf("func = %#x\n", func); // int res = func(1, 3); // 函数调用 最常见的形式 推荐使用 printf("res = %d\n", func(1, 3)); // 地址的形式 printf("res = %d\n", (&func)(1, 5)); // 指针的形式 printf("res = %d\n", (*func)(5, 5)); return 0; }
1.3 回调函数(重点)
-
函数指针变量做函数参数,这个函数指针变量指向的函数就是回调函数
-
回调函数可以增加函数的通用性
-
-
在不改变原函数的前提下,增加新功能
-
/** * *#include <stdio.h> int func(int a, int b) { return a + b; } int main() { // 函数在编译的时候 会有一个入口地址 // 这个地址就是函数指针地址 // 一个函数在编译时被分配一个入口地址,这个地址就称为函数的指针,函数名代表函数的入口地址 // printf("func = %#x\n", &func); // printf("func = %#x\n", *func); // printf("func = %#x\n", func); // int res = func(1, 3); // 函数调用 最常见的形式 推荐使用 printf("res = %d\n", func(1, 3)); // 地址的形式 printf("res = %d\n", (&func)(1, 5)); // 指针的形式 printf("res = %d\n", (*func)(5, 5)); return 0; } */ #include <stdio.h> int add(int a, int b) { return a + b; } int minus(int a, int b) { return a - b; } // 定义calac 方法 接受的参数 int int 接受计算类型的函数名称 int calac(int a, int b, int(*func)(int, int)) { return func(a, b); } int main() { // int res = add(10, 20); // 可以接受 参数 接受我们的计算类型 (加减……) // int res = calac(5, 3, add); int res = calac(5, 3, minus); printf("sum = %d\n", res); return 0; }
1.4 案例
// 计算圆的周长和面积 // 使用回调函数的形式 计算 // 圆的周长 pi * 2R // 面积 pi *r *r #include <stdio.h> #define pi 3.14 double len(double r) { return 2 * r * pi; } double size(double r) { return pi * r * r; } double calac(double r, double(*p)(double)) { return p(r); } int main() { double r = 5; double length = calac(r, len); double area = calac(r, size); printf("len = %lf\n", length); printf("size = %lf\n", area); return 0; }