指针的初版总结

本文详细解释了C语言中指针的作用、内存寻址、不同类型指针的使用、const修饰、野指针问题、指针运算、数组与指针传递、函数指针以及回调函数等内容,帮助读者掌握核心概念。
摘要由CSDN通过智能技术生成

什么是指针?

首先,C语言的目标是对内存的访问。在计算机系统中,CPU和内存是相互依存、密切配合的两个重要组成部分,它们共同决定了计算机的运行效率和性能。CPU主要对指令进行解析,执行,受限于芯片体积,功耗等因素,CPU内部很难做到大存储,这样CPU要执行的指令和数据必须外置,那么CPU如何访问到指令和数据呢?CPU要对它们进行寻址,才能找到它们。C语言给地址起了新名字,称为指针。

#include <stdio.h>
int main() {
    int a = 0;
    &a;  // &被称为取地址操作符,取出a占4个字节中较小字节的地址
    int *pa = &a;  // 取出a的地址存储到指针变量pa中  pa是int*类型
    //指针变量在32位机器中为4个字节(32/8bit),在64位机器中为8个字节
    char *pb = (char*)&a;   // (char *)让程序知道这是一个指向字符型数据的指针
    *pa = 10;   // 对于地址的使用,需要解引用操作符*,这里将 a 修改为10
    //解引用后访问时与基本数据类型有关,例如:char*访问1个字节,int*访问两个字节
    return 0;
}

那么如何描述一个地址?

两个方面,第一:这个空间有多大。第二:这个地址指向的空间如何访问,是一个字节一个字节的访问(即p+1)还是其他形式访问。

#include <stdio.h>
int main()
{
 int n = 10;
 char *pc = (char*)&n;
 int *pi = &n;
 
 printf("%p\n", &n);
 printf("%p\n", pc);
 printf("%p\n", pc+1);
 printf("%p\n", pi);
 printf("%p\n", pi+1);
 return 0;
}

void指针

可以在不知道具体数据类型的情况下,接受和处理任意类型的指针,并根据具体的类型进行相应的操作

void Print(void *ptr, char type) {
    if (type == 'i') {
        int *intP = (int *)ptr;
        printf("%d\n", *intP);
    } else if (type == 'f') {
        float *floatP = (float *)ptr;
        printf("%f\n", *floatP);
    } else {
        printf("未知类型\n");
    }
}

int main() {
    int a = 10;
    float b = 3.14;

    Print(&a, 'i'); // 传递整数类型的指针
    Print(&b, 'f'); // 传递浮点数类型的指针

    return 0;
}

const修饰指针

1.const修饰变量

应用const对变量加上一个限制,使起不能被修改,但是只是一个建议,实际上可以通过地址进行修改

int main() {
    const int n = 0;
    printf("n = %d\n", n);// 0
    int*p = &n;
    *p = 20;
    printf("n = %d\n", n);// 20
    return 0;
}

2.const修饰指针变量

1. const 放在*的左边:限制的是*p,意思是不能通过p来改变p指向的对象的内容,但是p本身是可以改变的,p可以指向其他对象。   const int *p = &n;

2. const 放在*的右边:限制的是p,意思是不能修改p本身的值,但是p指向的内容是可以通过p来改变的.     int * const p = &n;

int main()
{
  const int n = 10;
  int m = 100;
  const int * p = &n;
  *p = 20;      //err
  p = &m;       //ok
  return 0;
}
 
int main()
{
  const int n = 10;
  int m = 100;
  const int * const p = &n;
  *p = 20;     //ok
  p = &m;      //err
  printf("%d\n", n);
  return 0;
}

指针的运算

1.指针+-整数  :指针加上或减去整数时仍然是指针类型。指针加上整数会把指针向前移动一定距离,指针减去整数会把指针向后移动一定距离

2.指针-指针(运算的前提是两个指针指向了同一块空间):指针和指针之间的元素个数

注:无指针+指针

3.指针的关系运算

1)== ,!=  :用于比较两个指针是否指向同一个内存地址       

2)>,<,>=,<=用于比较两个指针所指向的内存地址的大小关系。比较的是指针指向的地址的值的大小

野指针

野指针的问题在于它指向的内存空间已经被释放,不再属于程序的所有权。这意味着针对这个指针进行的任何操作,如读取或写入,都是未定义的行为,可能导致程序崩溃或产生其他不可预测的结果。根据这个,引出它的成因和避免

1.成因

1)未初始化:  int *p;  

2)   越界访问:指针指向的范围超出数组范围时,p为野指针

3)  指针指向的内存空间释放:

#include <stdlib.h>

int main() {
    int* ptr;
    ptr = (int*) malloc(sizeof(int));
    *ptr = 10;  
    free(ptr); 
    
    return 0;
}

2.如何规避野指针

1)指针初始化 

2)小心指针越界

3)指针变量不再使⽤时,及时置NULL

4)避免返回局部变量的地址

assert断言

运行需要包含头文件assert.h,用于确保运行是否包含某条件 例如:assert(p!=NULL),若成立,继续运行。关闭时加上#define NDEBUG。

传值调用与传址调用

传值:把变量本⾝直接传递给了函数
传址:将变量的地址传递给了函数

注:

实参传递给形参的时候,形参会单独创建⼀份临时空间来接收实参,对形参的修改不影响实
参     例如 Swap函数
void Swap(int x, int y)
{
 int tmp = x;
 x = y;
 y = tmp;
}          //错误
============================================================
void Swap(int*px, int*py)
{
 int tmp = 0;
 tmp = *px;
 *px = *py;
 *py = tmp;
}         //正确

数组名

1)数组名就是数组⾸元素(第⼀个元素)的地址

2)sizeof(数组名),表示整个数组,计算的是整个数组的大小,单位是字节

3)&数组名,表示整个数组,取出的是整个数组的地址

4)整个数组的地址(+1操作是跳过整个数组的)和数组首元素的地址(+1跳一个元素)是有区别的,区别在哪里?就是在开头说的:这个地址指向的空间如何访问的。

一维传参的本质

一维数组传参的本质:传递的是数组首元素的地址

void test(int arr[])//参数写成数组形式,本质上还是指针
{
 printf("%d\n", sizeof(arr));
}
void test(int* arr)//参数写成指针形式
{
 printf("%d\n", sizeof(arr));//计算⼀个指针变量的⼤⼩
}

​​​​

函数形参的部分应该使用指针变量来接收首元素的地址。
那么在函数内部我们写 sizeof(arr) 计算的是一个地址的大小而不是数组的大小。正是因为函数的参数部分是本质是指针,所以在函数内部是没办法求的数组元素个数的。

二维数组传参本质

⼆维数组传参本质上也是传递了地址,传递的是第一行这个一维数组的地址
void test(int (*p)[5], int r, int c){   //包含5个整数的数组指针,表示二维数组的指针
 int i = 0;
 int j = 0;
 for(i=0; i<r; i++) {
     for(j=0; j<c; j++) {
        printf("%d ", *(*(p+i)+j));//*(p+i)表示第i行的地址,*(*(p+i)+j)表示第i行第j列元素的值
        }
     printf("\n");
     }
}

两者传参的本质:形参的部分可以写成数组的形式,也可以写成指针的形式

一些其他概念

二级指针

存放指针变量的地址

指针数组

存放指针的数组,可用来模拟二维数组
 int arr1[] = {1,1,1,1,1};
 int arr2[] = {2,2,2,2,2};
 int arr3[] = {3,3,3,3,3};
 //数组名是数组⾸元素的地址,类型是int*的,就可以存放在parr数组中
 int* parr[3] = {arr1, arr2, arr3};

字符指针变量

const char* pstr = "hello world!";  // 把字符串 hello world!⾸字符的地址放到了pstr中
 char str1[] = "hello bit.";
 char str2[] = "hello bit.";
 //数组名str1和str2在使用时会被编译器转换为一个指向数组第一个元素的指针,str1和str2实际上指向 
 //了两个不同的地址
 const char *str3 = "hello bit.";
 const char *str4 = "hello bit.";
 //两个指向字符常量的指针变量,它们所指向的字符常量相同,因此它们的地址也相同。

数组指针变量

int(*p)[10] = &arr;   //存放个数组的地址,就得存放在数组指针变量

函数指针

用来存放函数的地址,函数名就是函数的地址,也可以通过&函数名的方式获得函数的地址。

int (*p) (int x, int y)

函数指针数组

函数的地址存到⼀个数组中,那这个数组就叫函数指针数组
int (*parr1[3])();

sizeof和strlen的对比

1.sizeof是操作符
2.sizeof只关注占用内存空间的大小,不在乎内存中存放什么数据。

1.strlen是库函数使用需要包含头文件 string.h
2.srtlen是求字符串长度的,统计的是 \0 之前字符的个数
3.关注内存中是否有 \0

回调函数

将一个函数作为参数传递给另一个函数,并在需要的时候调用它。

把调用的函数的地址以参数的形式传递过去,使用函数指针调用

其他的总结:

int *p1[3];    p1  连续空间的首地址标识,空间有六个元素,每个元素都是地址,int 方式进行访问
int (*p2)[6]   p2  一把钥匙  连续空间访问内存,6个int6个int的访问
int *p3(double a) p3  升级为函数,输入参数double,返回类型是个地址
int (*p4)(double a)  p4  一把钥匙,函数调用方式访问内存
p4 是一个指针,指向一个接受一个double类型参数并返回int类型值的函数
int (*p5[5])(double a)   p5  钥匙串,函数调用方式访问
p5 是一个包含5个元素的数组,每个元素都是一个指向一个接受一个double类型参数并返回int类型值的函数的指针

补充

数组不是数据类型

为了表示多个同类型的连续空间,构造了一个数据结构的语法糖

数组名的含义

1.实际是常量指针
2.具有特殊的sizeof效果
3.数组名的地址是非法的,但被编译器重新定义了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值