C语言指针的自我介绍(你了解我吗?了解多少?)

点击蓝字

f512b06d672a11dea0a229c4dc89318e.png

关注我们

hey! Ladies and Gentlemen.😁欢迎大家来看望我,对,我就是指针(pointer),被很多人吐槽😔,也被人说好。我希望大家了解过我以后,能够爱上我😘。

大家在了解我之前 ,有必要清楚的知道数据在内存中是如何存取的。在我们写程序时,通常会定义一些变量,当程序进行编译时,系统会根据用户定义的变量类型分配一定长度的存储单元,储存用户需要存入的数据。在计算机的内存中,一个存储单元有一个地址,你定义了一个变量,该变量就有一个地址。我们形象的用一个比喻内存吧!有一栋宿舍楼,每间宿舍楼都有一个标号,我们就把这个标号认定为是地址,每间宿舍内有八张床,有人坐着的床位就认为是1,有人躺着的床位认为是0。当我们对变量的一些操作,例如赋值、取值。实际是对该变量的地址对应的内存操作。赋值时根据地址向内存中写入数据、取值时根据地址向内存中拿数据。

说明:一个存储单元可存储的大小的是8个二进制位,也就是一个字节。

例子:当我们定义了一个变量,但是并没有给该变量初始化,可以通过变量的地址,找到该地址中存储的内容。

f3ccb821f8afa74f940c24f178e3d4b4.png

说明:一个存储单元一个地址

ecc8884ce2414e03fb360cd40f7f08be.png

一、了解基本情况 

1、指针是什么

通常会说指针嘛,不就是就是地址吗。一个地址对应内存中的一个存储单元,地址指向该存储单元,为了形象化大家就把地址称为指针。指针变量就是储存指针的变量。在C语言中使用的时候,通常我们会把指针变量简单称为指针,指针变量和普通变量的定义方式不同,其次不同的是存储的内容一般是一个地址。

指针和指针变量的关系

  • 指针可以称为地址,地址也可称为指针

  • 指针变量是存放内存地址的变量

  • 指针和指针变量是两个不同的概念,但要注意的是,通常我们在使用时会把指针变量简为指针,实际上含义并不相同。

//指针变量定义方式:数据类型 *指针变量名 = 地址
int a = 10;
int *p = &a;//p是一个指针,p中储存a的地址, 指针p指向a

fd6657e2feb31a8d4cd0aadd272a6436.png

2、指针的类型 

通常来说,我储存了一个变量或对象的地址,我的类型和该变量或对象类型相同。我的类型也决定了我的访问权限,如果我是一个char*类型,我就只能能访问1个字节,是int *就只能访问4个字节。

int a = 10;
int *p1 = &a;//和p1*号结合,说明p1是一个指针,指针的类型是int *char b = 'a';
char *p2 = &b;//char *类型指针float c = 1.0;
float *p3 = &c;//float *类型指针

3、指针的初始化

如果你在定义我的时候,你没有我其他变量的地址,不妨初始化一个空地址给我,也就是NULL。既是善待我也是善待你自己。

char *pc = NULL;
short *ps = NULL;
int *pi = NULL;
long *pl = NULL;
float *pf = NULL;
double *pd = NULL;

4、指针的解引用

大家定义好以后,并给了我地址。解引用就是通过你给我的地址,我能通过你给定的地址,访问这块地址在内存中的内容。举前言的例子,取值时,相当于你让我去看看宿舍内哪些床是躺着人的,哪些是坐着人,然后向你汇报。赋值时相当于你让去把宿舍该躺下的人 让他们躺着,该坐着的的人让他们坐着。通常使用*+指针变量使用。

例子: 

int a = 10;int *p = &a;//通过取地址操作符&,获取a的地址,保存在指针变量p中
*p = 5;    //向变量a的地址写入数据5
int b = *p;//取出变量a地址中的内容赋给b

9abef0d9431f73eb777818496d1acb2b.png

5、请不要让我变成野孩子

这个名字给我取的😡,为啥称我是“野孩子”,也就是我是一块未知的地址,这个地址可能是随机的(不初始化),不正确的(指针越界)。如果你让我变为野孩子,那么我会让你失望的😟。

int main()
{    int *p;//局部变量未初始化,默认为随机值    *p  = 5;//error 对未知的内存区域写入操作    return 0;
}
int main()
{    int arr[10] = {0};    int *p = arr;    int i = 0;    for(i=0; i<=11; i++)   {      *(p++) = i;//当指针指向的地址超出arr可用的范围时,p就是野指针    }   return 0;
}

避免让我变成野孩子,从你做起!

  • 养成初始化的习惯

  • 小心我越界访问

  • 若指向的是动态开辟的内存地址,释放该内存后将我置为NULL

  • 不要返回局部变量的地址给我

  • 使用之前,检查我存储的地址是否有效。

大家能做到这几点,我谢谢大家了!

6、指针运算

//指针+-整数
int arr[5] = {1,2,3,4,5};int *p = arr;
//p指针是int *类型,+1指针指向的地址向高地址偏移四个字节,偏移量看指针的类型大小
//设p变量开始储存的地址是0x00000000,+1后指针指向的是0x00000004地址

aac4f8a3b5394bf430f57a48e3edc30b.png

//指针-指针
int my_strlen(char *s)
{   char *p = s;    while(*p != '')    {    p++;   } return p-s;    //可计算字符串长度,偏移元素个数
}

8f6756c91c8871bbb6b9244e16a937d2.png

7、指针和数组

f2cb4975d427cda46d22ef266630e47e.png

定义一个数组,数组会在内存中开辟一块连续的内存空间。数组名表示的是数组首元素的地址。既然是一个地址,那么就能给赋给指针变量,我就能通过该地址来存取。&arr表示的整个数组的地址。

5aece89c49fdfe7138ba803e064a25ee.png

51ff7e8dbad318556eef5adfba796aaa.png

大家可以发现:p+i就可以访问数组下标为i的地址,通过地址间接访问数组内容。数组下标和指针的关系

int arr[10] ={0};arr[0] = 1//等同于*(arr+0) = 1
arr[1] = 2//等同于*(arr+1) = 2

C语言中程序中,开辟出可以使用的内存,只要知道其地址,都能对内存进行访问。

8、升级的我(二级指针)

上面介绍的我都是一级的,来看看升级过后的我吧!之前我介绍的都是储存一个变量的地址升级过后的我就能储存我兄弟的地址。二级指针就是储存一级指针地址的指针。

164019048d754662fec82f7aa1d7c24a.png

你可以通过一次解引用访问指针变量p中的内容,两次解引用访问指针变量p地址储存的内容

30c089feecbbe230f82eeb22b3e9f524.png

9、指针的大小

因为我储存的是一个地址,在32位机器下,用32个二进制位表示,也就是四个字节,所以我的大小就是4字节。在64位机器下,用64个二进制位表示,8个字节,所以我的大小就是8字节。

二、深入了解我

1、字符指针

一般使用方法

int main()
{    char ch = 'w';    char *pc = &ch;       *pc = 'a';        return 0;
}

其他使用方式

23df2b4058ce559dc60d5a4fecd17132.png

知识补给站:C/C++会把常量字符串存储到单独的一个内存区域,当多个指针变量指向同一个字符串的时候,他们实际会指向同一块内存区域。但是如果用相同的常量字符串取初始化不同的字符数组就会开辟出不同的内存块。

经典例题:

#include <stdio.h>int main()
{        char str1[] = "Hello World";    char str2[] = "Hello World";    char* str3 = "Hello World";    char* str4 = "Hello World";   if (str1 == str2)     {        printf("str1 = str2");   }    else   {       printf("str1 != str2");   } if (str3 == str4)    {    printf("str3 = str4");   }    else   {       printf("str3 != str4");    }  return 0;
}

8aa10fe44e06cf67fdb5acfef96ba24d.png

2、指针数组(储存指针的数组)

//arr1是一个数组,数组中每一个元素储存都是int *类型的
int *arr1[5] = {NULL};//atr是一个数组,数组中每一个元素都是char *类型的指针
char *str[5] = {NULL};//arr2是一个数组,数组中每一元素都是char **类型的指针
char **arr2[5] = {NULL};

7d7a688e71f587fca3c08b8ed51ad9e1.png

227ddf05a28ef9dc49a29942a87eebee.png

3、数组指针 (指向数组的指针)

不知道大家会不会把指针数组和数组组成混淆

指针数组是一个数组,数组内部储存的是指针

数组指针是一个指针,指向一个数组的指针

aa461e37d02ba240c3c6d9cc01fc6a5b.png

int *p1[10] = {NULL};//指针数组int arr[10] = {0};
int (*p2)[10] = &arr;
/*数组指针,指针的类型是int[10]*
通常我们会说整形指针,字符指针、浮点指针,他们储存的是对应数据类型的地址,数组指针也相同指向的是一个数组类型的指针。
说明:C/C++中数组是内置的数据类型,但不是基础的数据类型,是构造的数据类型
p1 + 1 地址偏转4个字节
P2 + 1 地址偏转40个字节
*/

解释:[]的优先级高于*,所以()让p先和*结合,说明p是一个指针变量,指向的是一个大小为10个整形数据的数组。

int (*arr2[2])[5]//arr2先与[]结合,说明arr2是一个数组。int (*arr2[2])[5]//arr2是一个数组,数组中有2个元素,每个元素的类型是int[5] *,存放数组指针的数组。

611e7542957142725cbe8460171cdf27.png

说明:二维指针名表示二维数组中第一行的地址 。

4、数组传参和指针传参

一维数组传参

void test1(int arr[])//√
{}void test2(int arr[10])//√这个10写在里面无任何意义,但是该写法没错,形参部分的数组大小可以省略{}void test3(int *arr)//√
{}void test4(int *arr[10])//√
{}void test5(int **arr)//√
{}int main()
{  
int arr1[10] = {0};    
int *arr2[10] = {0};   test1(arr1);test2(arr1);   test3(arr1);  test4(arr2); //arr2是指针数组,arr2是该数组的首元素的地址,数组内,存的又是一级指针的地址test5(arr2);return 0;
}

二维数组传参

void test1(int arr[3][5])//√
{}void test2(int arr[][])//×
{}void test3(int arr[][5])//√
{}void test4(int *arr[5])//x
{}void test5(int (*arr)[5])//√
{}void test6(int **arr)//x
{}int main()
{    
int arr[3][5] = {0};   test1(arr);  test3(arr); test5(arr);}

总结:二维数组传参,函数形参的设计可以省略第一个[]的数字,因为对于一个二维数组,可以不知道有多少行,但是必须要知道一行有多少个元素。我们在定义二维数组的时候也可以省略行,但是不能省略列。

5、函数指针

f673ca75c9ed9d1e7f9136b412f9e70d.png

大家都知道,调用函数时,会在栈区开辟一块内存区域,既然有该函数开辟内存,那么该块内存就有一个地址。

937a275208744dcf8cd85f834fd3d4f8.png

test是函数的首地址,&test一个指向函数test的地址

//定义一个函数指针 函数返回值类型 (*指针变量名)(形参列表) = 函数地址
//使用方法   解引用(实际参数)void test()
{    
printf("Hello World");
}int main()
{   
void (*p1)() = &test;//p1是一个指针,指针的类型是void (*)()   (*p1)();//   
return 0;}
//错误定义方法:函数返回值类型 *指针变量名(形参列表)
void test()
{}
int main()
{    
void *p2() = test;//error    return 0;
}

6、函数指针数组

//定义方法:返回值类型 (*指针变量名[数组大小])(形参列表) ={地址1,地址2...};//p是一个数组,数组中的指针是 返回值类型 (*)(形参列表)类型的函数指针
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;
}int main()
{    
int (*p[4])(int x,int y) ={add,sub,mul,div};//p是一个数组,数组中存储的是函数指针   int i=0;    
for(i=0; i<4; i++)   {                            
printf("%d",(*p[i])(5,5));    } return 0;
}

5325254e5056530ecac5161b5d54dbb4.png

c591e3ba0147a7494d81c31c82b97eaf.jpeg

7、指向函数指针数组的指针 

int main()
{
//p是一个指针,指针类型是void (*)()
void (*p)() = NULL;//parr是一个数组,数组中元素的类型是void (*)()
void (*parr[5])() = {NULL}//pfarr是一个指针,指向的是一个指针数组,指针数组中元素的类型是void (*)()
void(*(*pfarr)[5])() = NULL; return 0;
}

8、回调函数

什么是回调函数:如果把函数的指针(地址)作为参数转递给另一个函数,当这个指针被用来调用其所指向的函数,我们就称为回调函数。回调函数不是由该函数的实现方直接调用。

int int_cmp(int a, int b)
{    
return a-b;
}void test(int (*p)(int , int ))//函数指针接收参数
{     
int t = (*p)(5,6);     
if(t>0)     {        
printf("a>b");     }    
else if(t == 0)     {       
printf("a=b");    }     
else{       
printf("a<b");     }}int main()
{    test(int_cmp);   return 0;
}

小题:解释如下代码的含义

(*(void (*)())0)()
//void (*)()是一种函数指针类型,把0强制转化为该种类型的指针。意味着调用0地址处,一个返回值类型是void无参的函数,0本来不是一个指针,经过强制转换后变成函数指针,对函数指针解引用就是调用该函数
void (*signal(int , void (*)(int)))(int)//signal是一个函数函数的返回值类型是void (*)(int )signal(int, void (*)(int))//表示的是一个函数的声明,signal函数名//可简化
typedef void (*pfun)(int )
pfun signal(int , pfun );

cd15b9800ff6b2068e9ffde97a0cc435.png

💎指针:感谢你的了解,更感谢你的 点赞 💖。

*声明:本文于网络整理,版权归原作者所有,如来源信息有误或侵犯权益,请联系我们删除或授权事宜。

96079209404a1b08b8b265e0ef21b956.png

91c1cfdbc5e0659f97abfedc50ae93a0.gif

戳“阅读原文”我们一起进步

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/337478.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

flex 解析json文件_使用 Python 处理 JSON 格式的数据 | Linux 中国

如果你不希望从头开始创造一种数据格式来存放数据&#xff0c;JSON 是一个很好的选择。如果你对 Python 有所了解&#xff0c;就更加事半功倍了。下面就来介绍一下如何使用 Python 处理 JSON 数据。-- Seth KenlonJSON 的全称是 JavaScript 对象表示法JavaScript Object Notati…

【C语言】指针进阶第一站:字符指针 typedef关键字!

点击蓝字关注我们简单回顾一下指针的概念内存会划分以字节为单位的空间&#xff0c;每一个字节都有一个编号&#xff08;地址/指针&#xff09;指针变量可以存放这个地址/指针注&#xff1a;我们日常所说的指针&#xff0c;一般是指针变量下面让我们坐上指针进阶的直通车&#…

python编译helloworld_python3学习笔记--001--python HelloWorld

python默认使用UTF-8编码 一个python3版本的HelloWorld代码如下&#xff1a; #!/usr/bin/env python print (Hello World!) 如果此python脚本文件名为&#xff1a;hello.py&#xff0c;则运行此脚本文件的方法有两种&#xff1a; 1、python hello.py [laolanglocalhost python]…

漫谈 C++:良好的编程习惯与编程要点

点击蓝字关注我们以良好的方式编写C class假设现在我们要实现一个复数类complex&#xff0c;在类的实现过程中探索良好的编程习惯。① Header(头文件)中的防卫式声明complex.h: # ifndef __COMPLEX__ # define __COMPLEX__ class complex {} # endif防止头文件的内容被多次包含…

【C语言】指针进阶第二站:指针数组!

点击蓝字关注我们指针数组数组是一种类型的数的集合整型数组的元素都是int类型指针数组的元素都是指针变量int* arr1[10];//整型指针的数组char*arr2[10];//一级字符指针的数组char** arr3[5];//二级字符指针的数组参考这一份示意图示例1:定义多个字符指针在上一站的字符指针里…

C语言初学者常见错误 | 总结22点

点击蓝字关注我们正文一.语言使用错误在打代码的过程中&#xff0c;经常需要在中文与英文中进行转换&#xff0c;因此常出现一些符号一不小心就用错&#xff0c;用成中文。例如&#xff1a;“&#xff1b;”中文中的分号占用了两个字节&#xff0c;而英文中“;”分号只占用一个…

nginx配置vue项目500_一个Nginx部署多个vue前端项目总结

摘要&#xff1a;近来接手了一个二次开发的前后端分离模式的项目&#xff0c;其中在前端项目的部署上需要让2个前端项目都部署到一个IP地址和端口下&#xff0c;那么我们这里就要用到Nginx了&#xff0c;接下来我们看看如何在一个Nginx下部署2个前端项目的编译打包2个前端项目执…

【C语言】指针进阶第三站,数组指针!

点击蓝字关注我们数组指针整型指针&#xff1a;指向整型的指针字符指针&#xff1a;指向字符的指针数组指针&#xff1a;指向数组的指针基本概念下面哪个是数组指针呢&#xff1f;指针数组和数组指针的概念很容易混淆&#xff0c;一定要分清楚哦&#xff01;int *p1[10]; int (…

【C语言】指针进阶第四站:数组/指针的传参问题!

点击蓝字关注我们朋友们&#xff0c;到站啦&#xff01;指针进阶第四站&#xff1a;传参问题0.引例自定义函数里形参的类型&#xff0c;要和函数调用中传过去的实参类型相对应test函数里的是int类型&#xff0c;我们传过去的参数a也是int类型void test(int n) {} int main() {i…

python做自动化控制postman_python自动化测试入门篇-postman

接口测试基础-postman 常用的接口有两种&#xff1a;webservice接口和http api接口。 Webservice接口是走soap协议通过http传输&#xff0c;请求报文和返回报文都是xml格式。 http api接口是走http协议&#xff0c;通过路径来区分调用的方法&#xff0c;请求报文都是key-value形…

Python3实现红黑树[上篇]

Python3实现红黑树[上篇]由于时间有限&#xff0c;这次只写了红黑树添加节点&#xff0c;关于节点的删除放在下一讲 https://blog.csdn.net/qq_18138105/article/details/105324025。 关于红黑树的介绍&#xff0c;来由&#xff0c;性质和定义&#xff0c;可以看这篇文章&…

web应用程序并发测试_测试并发应用

web应用程序并发测试本文是我们名为Java Concurrency Essentials的学院课程的一部分。 在本课程中&#xff0c;您将深入探讨并发的魔力。 将向您介绍并发和并发代码的基础知识&#xff0c;并学习诸如原子性&#xff0c;同步和线程安全性的概念。 在这里查看 &#xff01; 目录…

深入理解存储器层次结构

点击蓝字关注我们1概述对于一个简单的计算机系统模型&#xff0c;我们可以将存储器系统看做是一个线性的字节数组&#xff0c;而 CPU 能够在一个常数时间内访问每个存储器的位置。实际上&#xff0c;存储器系统&#xff08;memory system&#xff09;是一个具有不同容量、成本和…

C++的一个指针占内存几个字节?

C的一个指针占内存几个字节&#xff1f;结论&#xff1a; 取决于是64位编译模式还是32位编译模式&#xff08;注意&#xff0c;和机器位数没有直接关系&#xff09; 在64位编译模式下&#xff0c;指针的占用内存大小是8字节在32位编译模式下&#xff0c;指针占用内存大小是4字…

【C语言】指针进阶第五站:函数指针!

点击蓝字关注我们函数指针函数也有自己的地址&#xff0c;函数名/&函数名 就是函数的地址1.1基本形式在 数组指针的学习中我们了解到int arr[5]; int (*pa)[5] &arr;//pa是数组指针指针变量pa的类型是int(*)[5]那么函数指针的形式是怎样的呢&#xff1f;void test(cha…

jsp 体检信息查询 绕过用户名验证_一篇彻底搞懂jsp

jsp 实栗 jsp jdbc 实现登录实现思路一个表单页&#xff0c;输入用户登录和密码&#xff0c;然后信息提交到jsp页面进行验证&#xff0c;如果可以服务器跳转到登录成功页&#xff0c;失败&#xff0c;跳转到错误页跳转的时候窗口的URL地址会发生变化代码如下编写登录代码登录&…

C/C++与汇编混合编程有什么好处?

点击蓝字关注我们1 导语 当需要C/C与汇编混合编程时&#xff0c;可以有以下两种处理策略&#xff1a;若汇编代码较短&#xff0c;则可在C/C源文件中直接内嵌汇编语言实现混合编程。若汇编代码较长&#xff0c;可以单独写成汇编文件&#xff0c;最后以汇编文件的形式加入项目中&…

centos 7.6安装java_Hadoop的安装

为了方便后面使用Hadoop的shell命令&#xff0c;我先介绍Hadoop的安装。Hadoop有多种安装模式&#xff0c;这里介绍伪分布式的安装。我测试过Ubutun、Centos和WSL&#xff0c;都可以正常安装Hadoop的所有版本。所有一般不会出现版本对应的问题。Hadoop是基于Java语言进行编写的…

C++软件分析师异常分析工作经验汇总

点击蓝字关注我们最近几年工作当中很大一部分内容是排查软件运行过程中遇到的各种异常&#xff0c;积累了一定的经验&#xff0c;在此给大家分享一下。本文将详细讲述Windows系统中软件异常的分类以及常用的排查方法&#xff0c;给大家提供一个借鉴与参考。1、软件异常的分类常…

java fix_Java中的低延迟FIX引擎

java fix总览 Chronicle FIX是我们的Low Latency FIX引擎和Java数据库。 是什么使它与众不同&#xff1f; 是为Java中的超低GC *设计的。 支持字符串和日期时间的方式可以最大程度地减少垃圾和开销。 可自定义为仅包含您期望的字段。 使用通常在二进制解析器和生成器中使用…