指针
指针是C语言的精华,没有掌握指针,就是没有掌握C语言。
指针让 C 语言更像是结构化的底级语言,所有在内存中的数据结构均可用指针来访问,指针让 C 语言带来了
更简便的操作,更优效率,更快的速度。是天使也是魔鬼。
一、认识内存
无论是前面学习的变量还是后面我们要学习的数组,都是逻辑上得表现,最终都要保存到内存中,而内存是线性的,这是物理基础。
内存是以字节为单位进行编址的,内存中的每个字节都对应一个地址,通过地址才能找到每个字节。
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;
}