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;
}
​

  • 14
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值