当前位置: 首页 > news >正文

指针(c语言)

一.指针的定义

1.内存被划分为一个一个内存单元,对内存单元进行编号,这些编号称为内存单元的地址

指针就是存放地址的容器

2.平常说的指针,其实就是指针变量

注意:

1个内存单元的大小是1个字节,如果是一个int类型的变量,就会有4个内存单元


二.指针变量

我们可以通过&(取地址)操作符来取出变量的内存其实就是地址,把地址放在一个变量中,这个变量就是指针变量

#include <stdio.h>int main(){int a = 10;//在内存中开辟一块空间
int *p = &a;//这里我们对变量a,取出它的地址,可以使用&操作符。
//a变量占用4个字节的空间,因为它是int类型,这里是将a的4个字节的第一个字节的地址存放在p变量
中,p就是一个之指针变量。
return 0;}

1.指针变量的大小

1.在32位机器地址是由二进制(0或1)组成的,1个二进制就是1个bit(比特),32位二进制,那也就是32个bit(比特),比特转换为字节是8进1,所以就是4个字节

2.在64位机器上,也就是有64个二进制,也就是64bit(比特位) ,也就是8个字节

总之:在32位机器上,指针变量的大小是4个字节

           在64位机器上,指针变量的大小是8个字节    


三.指针和指针的类型

指针有整数类型,浮点数类型,字符类型

那他们的大小怎么样,如下

总之:

在64位(x64)操作系统,指针类型大小统一为8个字节

在32位(x86)操作系统下,指针类型大小统一为4个字节

1.指针类型的意义

指针类型决定了+1或者-1时往后访问几个字节

若int、float、double。short类型变量转成char类型指针,+1往后访问1个字节,-1往前访问1个字节

若char类型变量转成int、float、double。short类型指针,   +1往后访问4个字节,-1往前访问4个字节

2.指针+-整数

int main()
{int n = 10;char* pc = (char*)&n;int* pi = &n;//pc是指针变量存放n的指针,所以他们俩的地址相同printf("%p\n", &n);printf("%p\n", pc);//把整数类型转换为字符类型,由4个字节变成1个字节//所以pc+1是往后访问1个字节printf("%p\n", pc + 1);printf("%p\n", pi);//这是整数类型的指针,+1往后访问4个字节printf("%p\n", pi + 1);return  0;
}

运行结果

这里显示的是十六进制👆

那么为什么000000DA66EF9F4到000000DA66EF9F5增加了1个字节呢?

因为内存被划分为了一个个内存单元,每个内存单元的大小是1个字节内存单元里头放的是地址

那么000000DA66EF9F4到000000DA66EF9F5,也就是走到了下一个内存单元,所以就增加了1个字节。

3.指针-指针

C 语言规定

这个减法操作的结果是两个指针所指向的地址之间相隔的元素个数。

int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };printf("%p\n", &arr[0]);printf("%p\n", &arr[9]);int n = &arr[9] - &arr[0];printf("%d\n", n);return 0;
}

程序运行👇

例:实现strlen函数的功能

char my_strlen(int* pa)//用指针来接受数组名的首元素的地址
{char* start = pa;while (*start !='\0')//字符串是以'\0'结尾,排除'\0'的情况.{//刚好访问到了\0的地址,但因*start !='\0'条件跳出循环start++;}return start - pa;
}int main()
{char arr[] = "abcdef";/*int ret = my_strlen(arr);*/printf("%d", my_strlen(arr));//数组名代表了首元素的地址,所以不用取地址return 0;
}

代码运行

4.指针的关系运算

//宏定义
#define N_VALUES 5
//创建数组
int values[N_VALUES];
//创建指针变量
int* vp;int main()
{for (vp = &values[N_VALUES]; vp > &values[0];){//先对其解引用,后再-1.*--vp = 0;}int i = 0;for (i = 0; i < 5; i++){printf("%d ", values[i]);}return 0;
}

代码运行


四.指针和数组

1.数组名代表首元素的地址

注意:

1.sizeof(arr):这里头数组名表示的是整个数组,计算的是整个数组的大小。

2.对数组名取地址,表示的是整个数组的地址。

1.应用指针对数组进行遍历

        数组元素是连续存放的,并且由低地址想高地址进行存放的,那么就可以对指针变量+1或者-1,找到数组中元素所对应地址,对其解引用,进而访问数组中的元素。

代码如下:

实现数组的遍历

int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };int* pa = arr;int i = 0;for (i = 0; i < 10; i++){//先对指针变量解引用,进而访问数组中的元素,后再++printf("%d ", *(pa++));}return 0;
}


五.野指针

概念:野指针就是指针指向的位置是不可知(随机的,正确的,没有明确限制)

1.野指针成因

  指针未初始化

#include <stdio.h>int main(){   
int *p;//局部变量指针未初始化,默认为随机值
*p = 20;return 0;}

指针越界访问

include <stdio.h>int main(){int arr[10] = {0};int *p = arr;int i = 0;for(i=0; i<=11; i++){//当指针指向的范围超出数组arr的范围时,p就是野指针
*(p++) = i;}return 0;}

2.如何避免出现野指针

1.指针初始化

2.小心指针越界

3.指针指向空间释放,及时置NULL

4.避免使用局部变量的地址

5.指针使用之前检查其有效性


六.二级指针

一级指针存放的是变量的地址,那么二级指针存放的是一级指针的地址

#include<stdio.h>
int main()
{int a =10;
//一级指针存放的是a的地址int* pa =&a;
//二级指针存放的是pa这个指针变量的地址int** ppa=&pa;return 0;
}

图解如下👇

七.字符指针

字符指针存放字符的指针,准确的来说,是把字符首元素的地址存放在指针变量

int main()
{//字符串的首元素的地址被存放再pstr的指针变量了const char* pstr = "Hello";//直接打印字符串printf("%s\n", pstr);return 0;
}

代码运行

例子

#include <stdio.h>
int main()
{char str1[] = "hello bit.";char str2[] = "hello bit.";const char* str3 = "hello bit.";const char* str4 = "hello bit.";if (str1 == str2)printf("str1 and str2 are same\n");elseprintf("str1 and str2 are not same\n");if (str3 == str4)printf("str3 and str4 are same\n");elseprintf("str3 and str4 are not same\n");return 0;
}

运行结果

str1[]和str2[]是俩个数组,他们俩个数组的地址不相同,所以ptr1 !=ptr2

str3和str4存放的是同一个字符串的地址,所以str3和str4地址一样,即str3 ==str4


八.指针数组

指针数组就是存放指针的数组

int* arr[]={&a,&b,&c};

arr是一个数组,数组中由三个元素,且是元素的地址

int main()
{int a = 10;int b = 20;int c = 30;//指针数组//存放地址的数组int* arr[] = { &a,&b,&c };int i = 0;while (i < 3){//对其解引用,进而通过地址访问数组中的元素printf("%d ", (*arr[i]));i++;}return 0;
}

1.二级指针的指针数组

        int **arr3[5];


九.数组指针

数组指针指的是存放数组地址的指针

   int (*p)[10];

解释:p先和*结合,说明p是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以p是一个 指针,指向一个数组,叫数组指针。

        [ ]的优先级是高于 *,所以需要对其加()

利用数组指针的知识实现对一维遍历

int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };int sz = sizeof(arr) / sizeof(arr[0]);int(*p)[10] = &arr;int i = 0;//遍历,打印元素for (i = 0; i < sz; i++){//这俩个printf等价//*p代表数组指针printf("%d ", *((*p) + i));//printf("%d",(*p)[i])}return 0;
}

代码运行

利用数组指针对二维数组实现遍历

二维数组的起始是一维数组的数组

二维数组中每一行都当成一个首元素

所以二维数组的第一行是首元素的地址

十.一维数组的传参

以下5种方式均可用于一维数组的传参

#include <stdio.h>void test(int arr[]){}void test(int arr[10]){}void test(int *arr)/{}void test2(int *arr[20]){}void test2(int **arr){}int main(){int arr[10] = {0};

十一.二维数组的传参

//二维数组的传参
void test(int arr[3][5])
{}
//二维数组行可以省略,列不可以省略
void test(int arr[][5])
{}
//利用数组指针,存放二维数组的首地址
void test(int(*arr)[5])
{}int main()
{int arr[3][5] = { 0 };return 0;
}

十二.函数指针

函数指针就是存放函数的指针

这俩个代码即使没有对test取地址,得到的函数的地址都是一样,这说明了函数名已经表示了函数的地址,所以对不对其取地址都一样。类似于数组

    int(*pf)(int, int) = Add;

首先pf首选与*结合说明他是一个指针变量,然后指向一个函数,指向的函数有俩个参数,返回类型是int,说明它是一个函数指针

函数调用

void Add(int x, int y)
{int result = x + y;printf("%d ", result);
}int main()
{//存放函数的指针,就是指针变量int(*pf)(int, int) = Add;//这三种调用方式都可以//通过解引用函数指针进行调用(*pf)(2,3);//函数指针可以直接调用,编译器会自动处理pf(1, 1);Add(0, 0);return 0;
}

代码运行


十三.函数指针数组

函数指针数组就是把函数的地址存放在一个数组中

   int ( *parr1[10] ) ( );

parr1先与[  ]结合说明他是一个数组,存放的是int (*)()类型的函数指针

例题

十四:指向函数指针数组的指针

int Add(int x, int y)
{return x + y;
}
int Sub(int x, int y)
{return x - y;
}int main()
{//函数指针int(*pf)(int, int) = Add;//函数指针数组int(*pfArr[4])(int, int) = { Add,Sub };int(*(*pffArr)[4])(int, int) = &pfArr; //pffArr是一个指向函数指针数组的指针变量//首先pffArr与*结合说明他是一个指针,该指针指向一个方括号说明他是指向的数组//并且数组中的每一个元素是函数指针return 0;
}

十五.回调函数

回调函数的定义:

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个 函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数

回调函数不是由该函数 的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进 行响应。

例题:计算器的+ - * /的实现

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
//制作菜单
void menu()
{printf("***********************\n");printf("***********************\n");printf("*******1.add  2.sub********\n");printf("*******3.mul  4.div****\n");printf("*********0.exit***********\n");printf("***********************\n");printf("***********************\n");
}
//使用函数指针来实现函数回调
int Add(int x, int y)
{return x + y;
}
int Sub(int x, int y)
{return x - y;
}
int Mul(int x, int y)
{return x * y;
}
int Div(int x, int y)
{return x / y;
}void Calc(int(*pa)(int, int))
{int x = 0;int y = 0;int ret = 0;printf("请输入俩个操作数");scanf("%d %d", &x, &y);//通过函数指针来调用函数,比如说Add函数,pa就是等价于Add函数//不需要对其解引用,编译器会自动处理ret = pa(x, y);printf("%d\n", ret);
}
int main()
{int input = 0;do{menu();printf("请选择\n");scanf("%d", &input);switch (input){case 1://函数名就是函数的地址,把函数的地址传递给形参Calc(Add);break;case 2:Calc(Sub);break;case 3:Calc(Mul);break;case 4:Calc(Div);break;default:break;}} while (input);return 0;
}


结语:作为初学者,可能对指针理解不太全面,本篇文章不足之处在所难免,如有错误还望大家纠正下,望大家多多包涵,谢谢大家!


http://www.mrgr.cn/news/65622.html

相关文章:

  • 一篇文章理解CSS垂直布局方法
  • Python 条件语句
  • Qt开发技巧(二十二)设置QPA,打开记忆文件,清除表单页注意判断存在性,工程文件去重添加,按钮组的顺序设置,Qt的属性用来传值,查找问题的方法
  • Css-常用指令大全
  • ubuntu20.04 加固方案-设置用户缺省UMASK
  • Maven scope参数详解
  • gulp入门教程9:lastRun
  • [算法初阶]第二集 滑动窗口(已完结)
  • 宠物空气净化器是不是智商税?真实测试热门品牌!哪款除毛好?
  • 在VScode中配置C_C++环境
  • ROS2 单帧Pcd转多帧节点 录制Bag
  • 截至2024年10月, 数据知识产权登记分析
  • ArkTS常用数据处理:掌握核心技能与实践
  • GS-SLAM论文阅读--High-Fidelity SLAM Using Gaussian Splatting
  • DoubletFinder报错小结
  • 人工智能----Ai普及---手机App
  • 8、raid磁盘阵列
  • C++线程池
  • sklearn红酒数据集分类器的构建和评估
  • 图说复变函数论重大错误:将无穷多各异平面误为同一面
  • 智慧医疗——提出了一种基于敌对领域适应症预测候选抗癌药物的方法
  • 江协科技STM32学习- P35 硬件I2C读写MPU6050
  • 信息安全工程师(74)网络安全风险评估技术方法与工具
  • 633. 平方数之和 中等
  • 总结拓展十五:SAP物料分割评估
  • MATLAB绘图基础10:MATLAB极坐标相关图形