所以说:指针就是地址,人们口中的指针变量也是指针。
我们可以通过&
(取地址操作符)取出变量的内存与实地址,把地址可以存放到一个变量中,这个变量就是指针变量。
int main()
{
int c = 520; //将520赋给c
int* ch = &c; //将整形c的地址取出来放在ch中
printf("%p", ch);
return 0;
}
指针的大小在32位平台是4个字节,在64位平台是8个字节。
是的,不同类型的变量的地址就应该放在对应的指针变量中。
int main()
{
int a = 0;
int* pa = &a;
double b = 0;
double* pb = &pb;
char c = 'w';
char* pc = &c;
return 0;
}
那是因为不同类型的指针±整数所跳过的字节数不同。
int main()
{
int a = 0;
int* pa = &a;
printf("%p %p\n", pa, pa + 1);
double b = 0;
double* pb = &pb;
printf("%p %p\n", pb, pb + 1);
char c = 'w';
char* pc = &c;
printf("%p %p\n", pc, pc + 1);
return 0;
}
从上图中我们可以看出:
int*
类型的指针 + 1 是跳过四个字节
double*
类型的指针 + 1 是跳过八个字节
char*
类型的指针 + 1 是跳过一个字节
诶,看着很眼熟??
int
类型变量的大小是 四个字节?
double
类型变量的大小是八个字节?
char
类型变量的大小是一个字节?
难道?难道?没错!!!就是你猜想的那样…
指针±整数对应的是指针向前/向后移动的大小(指针指向变量类型大小 * 整数)
你猜对了!!一半 !!
指针 + 指针
是没有意义的,但是指针 - 指针
是有意义的哟!!
指针 - 指针
的结果是两个指针之间所隔的元素个数,这种操作通常用于计算数组中两个元素之间的距离。
指针的作用就是通过地址取访问指针指向的变量。
指针的类型决定了指针解引用能够访问的字节数。
例如上面的int*
类型的指针,解引用能访问四个字节,double*
类型的指针可以访问八个字节,char*
类型的指针能够访问一个字节
野指针是指指向未知内存位置或者已经释放内存的指针。
引用未初始化的指针、访问已释放内存、数组边界越界等行为都可能导致野指针。
int main()
{
int* pa = NULL;
printf("%d", *pa);
return 0;
}
int main()
{
char ch[4] = "Love";
for (int i = 0; i <= 4; i++)
{
printf("%c ", ch[i]);
}
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
//动态申请4个char类型大小的空间
char* ch = (char*)malloc(sizeof(char) * 4);
if (ch == NULL)
{
return 0;
}
for (int i = 0; i < 4; i++)
{
ch[i] = 'a' + i;
}
//申请来的ch释放,内存还给操作系统
//这时候访问ch中的元素就会造成野指针,打印出随机值
free(ch);
printf("%c", ch[0]);
return 0;
}
首先先展示一下指针和数组的例子
int main()
{
int arr[] = {
5 , 2 , 0 };
int* pa = &arr[0];
printf("%p\n", arr);
printf("%p\n", pa);
return 0;
}
从上面的结果中我们可以发现:
arr
是数组名,pa
是首元素的地址,而两者却相等。
结论:数组名大多数情况都是首元素地址。
例外:(数组的地址和数组首元素的地址的区别后面会讲)
sizeof(数组名)
,这里的数组名是数组的地址&(数组名)
,这里的数组名也是数组的地址那么指针(首元素地址)±整数与&数组名[下标]有什么关系呢??
int main()
{
int arr[] = {
1 , 3 , 1 , 4 , 5 , 2 , 0 };
int* pa = &arr[0];
for (int i = 0; i < 7; i++)
{
printf("&arr[%d]: %p <---> pa + %d: %p\n", i, &arr[i], i, pa + i);
}
return 0;
}
从上面的图中我们可以得出:指针(首元素地址)±整数与&数组名[下标]是相同的。
那么能用下标遍历数组,能用指针(地址)±整数遍历吗??
int main()
{
int arr[] = {
1 , 3 , 1 , 4 , 5 , 2 , 0 };
int* pa = &arr[0];
for (int i = 0; i < 7; i++)
{
printf("arr[%d]: %d <---> *(pa + %d): %d\n", i, arr[i], i, *(pa + i));
}
return 0;
}
从上图中可以得知:下标和指针(地址)±整数都能够遍历数组。
我们又知道pa是首元素地址,数组名也是首元素地址,那么数组名±整数能遍历数组吗??
int main()
{
int arr[] = {
1 , 3 , 1 , 4 , 5 , 2 , 0 };
int* pa = &arr[0];
for (int i = 0; i < 7; i++)
{
printf("arr[%d]: %d <---> *(arr + %d): %d\n", i, arr[i], i, *(arr + i));
}
return 0;
}
从上图中可以得出:下标和数组名±整数都可以遍历数组。
数组名可以代替首元素地址,那么首元素地址能够代替数组名吗??
int main()
{
int arr[] = {
1 , 3 , 1 , 4 , 5 , 2 , 0 };
int* pa = &arr[0];
for (int i = 0; i < 7; i++)
{
printf("pa[%d]: %d <---> *(pa + %d): %d\n", i, pa[i], i, *(pa + i));
}
return 0;
}
从上图中我们可以得出:数组首元素地址可以代替数组名。
结论:(我们可以通过指针直接访问数组)
指针(首元素地址)+-整数
与&数组名[下标]
是相同的。指针(首元素地址)+-整数
都能够遍历数组。数组名+-整数
都可以遍历数组。arr[i] = *(arr + i) = *(pa + i) = pa[i]
变量有地址,指针变量也是变量,那么指针变量也有地址吗??
当然,指针变量也有地址,而存储指针的变量叫做二级指针。
int main()
{
int arr[] = {
1 , 3 , 1 , 4 , 5 , 2 , 0 };
int* pa = &arr[0];
int** ppa = &pa;
printf("ppa : %p\n", ppa);
printf(" pa : %p\n", pa);
printf("*ppa: %p\n", *ppa);
return 0;
}
二级指针存储的是一级指针的地址,而指针解引用可以找到被指针指向的变量,那么这里二级指针解引用也可以找到一级指针。
对于字符指针我们常见的使用方法是先创建一个字符变量,再创建一个字符指针变量,将字符变量的的地址赋给字符指针变量。
int main()
{
char c = 'v';
char* ch = &c;
printf("%p", ch);
return 0;
}
另一种使用方法则是创建一个字符指针变量,将字符串的首元素地址赋给他。
注意:这里是将字符串中的首元素地址存在 ch
中,而非字符串的地址。
int main()
{
char* ch = "chineseperson04";
printf("%p", ch);
return 0;
}
指针数组是一种存储指针的数组。
int main()
{
int arr1[] = {
5 , 2 , 1 , 1 , 3 , 1 , 4 };
int arr2[] = {
5 , 2 , 0 , 1 , 3 , 1 , 4 };
int arr3[] = {
9 , 4 , 2 , 0 , 0 , 0 , 0 };
int* arr[] = {
arr1 , arr2 , arr3 };
int i = 0;
for (i = 0; i < 3; i++)
{
printf("%p\n", arr[i]);
}
return 0;
}
指针数组是存储一级指针的数组,而一级指针指向的是数组,有没有发现指针数组的使用与二维数组很想,但两者却不能看成一个东西。
int main()
{
int arr1[] = {
5 , 2 , 1 , 1 , 3 , 1 , 4 };
int arr2[] = {
5 , 2 , 0 , 1 , 3 , 1 , 4 };
int arr3[] = {
9 , 4 , 2 , 0 , 0 , 0 , 0 };
int* arr[] = {
arr1 , arr2 , arr3 };
int i = 0;
for (i = 0; i < 3; i++)
{
for (int j = 0; j < 7; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
return 0;
}
数组指针是用来存放数组地址的指针。
数组指针变量如何定义:(假设这里的指针变量是 p
,数组存放的 int
类型的变量)
首先它是指针那么就不能与[ ]先结合----(*p)
其次它指向的内容是数组 ---- (*p)[ ]
----[ ]中为数组的元素个数
最后它指向数组存储变量的类型为什么 ---- int(*p)[]
int main()
{
int arr[10] = {
0 , 1, 2, 3, 4, 5, 6, 7, 8, 9 };
int(*p)[10] = &arr;
printf("%p", p);
return 0;
}
区别:( &arr 与 arr )
那么取出数组的地址和数组首元素的地址有什么区别呢??
这里我们用代码测试一下!!
int main()
{
int arr[10] = {
0 , 1, 2, 3, 4, 5, 6, 7, 8, 9 };
printf("%p\n", &arr); //取出数组的数组
printf("%p\n", arr); //取出数组首元素的地址
printf("%p\n", &arr[0]); //取出数组首元素的地址
return 0;
}
这里取出数组的地址和数组首元素的地址好像没什么区别,那么它们就是一样的吗??
这里我们进入下一个测试环节,让它们取出来的地址 +1
试试。
int main()
{
int arr[10] = {
0 , 1, 2, 3, 4, 5, 6, 7, 8, 9 };
printf("%p\n", &arr); //取出数组的数组
printf("%p\n", arr); //取出数组首元素的地址
printf("%p\n", &arr[0]); //取出数组首元素的地址
printf("\n");
printf("%p\n", &arr + 1);
printf("%p\n", arr + 1);
printf("%p\n", &arr[0] + 1);
return 0;
}
诶?这里 +1
得到的地址不同,分析一下。
(1)数组首元素地址 +1
:这里的地址在十六进制的状态下,相比之下地址的大小增加了 4
,十进制下也是 4
,数组储存变量是 int
内存大小也是 4
个字节 ,这里我们可以得到数组首元素地址 +1
,也就是跳过了一个变量大小的字节数。
(2)数组地址 +1
:这里的地址在十六进制的状态下,相比之下地址的大小增加了 28
,十进制下就是 40
,数组储存变量是 int
内存大小也是 4
个字节并且数组元素个数是 10
个元素 ,这里我们可以得到数组首元素地址 +1
,也就是跳过了一个数组大小的字节数。
结论:
+1
,跳过一个变量的大小。+1
, 跳过一个数组的大小函数指针是一种指向函数的指针变量,它存储着函数的地址。函数指针的类型由函数的返回值类型和参数类型组成,可以用以下语法定义:
返回值类型(*指针变量名)(变量列表)
这里写一个例子:
int Add(int x, int y)
{
return x + y;
}
int main()
{
int (*pf)(int, int) = &Add;
int a = 10, b = 30;
int c = 0;
c = (*pf)(a, b);
printf("%d", c);
return 0;
}
int Add(int x, int y)
{
return x + y;
}
int main()
{
int (*pf)(int, int) = &Add;
printf("%p\n", &Add);
printf("%p\n", Add);
return 0;
}
这里我们得到 &(函数名) vs (函数名)
是一样的。
那么这里就可以进行下面的推理:
int Add(int x, int y)
{
return x + y;
}
int main()
{
int (*pf)(int, int) = &Add;
int a = 10, b = 30;
int add1 = 0 , add2 = 0;
int add3 = 0, add4 = 0;
//add1 通过函数名掉用函数
add1 = Add(a, b);
printf("%d\n", add1);
//add2 通过函数指针存储函数的地址,再解引用找到函数,调用函数
add2 = (*pf)(a, b);
printf("%d\n", add2);
//由于上面得到 (&Add) 与 Add 相同
//那么这里 ( pf 对应 &Add ) 与 ( (*pf) 对应 ADD )
//所以这里可以得到这里的 * 是没有用的,下面的测试也证明了这一点
add3 = pf(a, b);
printf("%d\n", add3);
add4 = (******pf)(a, b);
printf("%d\n", add4);
}
函数指针数组是一个数组用来存储函数指针的。
如何定义一个函数指针数组:假设数组存储的是 int (*)(int ,int)
首先是一个数组:那么就要先于[]
结合 ---- pf[]
然后数组存储的是函数指针:int(*pf[])(int ,int)
----方块中的是元素个数
这里使用函数指针数组完成一个简易版的计算器。
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;
}
calculate(int (*pf)(int, int))
{
int x = 0, y = 0;
printf("请输入两个操作数\n");
scanf("%d%d", &x, &y);
printf("%d\n", pf(x, y));
}
int main()
{
int (*pf[4])(int, int) = {
add , sub , mul , div };
int option = 0;
while (1)
{
printf("*************************\n");
printf("**** 1:add 2:sub ****\n");
printf("**** 3:mul 4:div ****\n");
printf("*************************\n");
printf("请选择:>");
scanf("%d", &option);
switch (option)
{
case 1:
calculate(add);
case 2:
calculate(sub);
case 3:
calculate(mul);
case 4:
calculate(div);
default :
printf("输入错误,请重新选择\n");
}
}
return 0;
}
如何定义一个函数指针数组的指针:假设数组存储的是 int (*)(int ,int)
首先是一个指针:那么就要先于*
结合 ---- (*pf)
然后指针指向的是函数指针数组:(*pf)[]
----方块中的是元素个数
最后数组存储的元素类型是:int(*(*pf)[])(int ,int)
----方块中的是元素个数
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 (*pf[4])(int, int) = {
add , sub , mul , div };
int (*(*ppf)[4])(int, int) = &pf;
return 0;
}
回调函数是一种函数,它作为参数传递给另一个函数,并且在其它函数执行完特定操作后被调用。
这里使用回调函数实现 qsort()
函数的模拟(原理不同,这里使用冒泡排序的底层原理)
void Bubble_sort(void* base , size_t num , size_t width,
int (*Cmp)(const void * p1 , const void* p2)) //这里函数返回值类型需要按需求改
{
size_t i = 0;
for (i = 0; i < num - 1; i++)
{
for (size_t j = 0; j < num - i - 1; j++)
{
if (Cmp((char*)base + j * width , (char*)base + (j + 1) * width) > 0 )
{
Swap((char*)base + j * width, (char*)base + (j + 1) * width , width);
}
}
}
}
如果有什么建议和疑问,或是有什么错误,大家可以在评论区中提出。
希望大家以后也能和我一起进步!!
如果这篇文章对你有用的话,希望大家给一个三连支持一下!!
文章浏览阅读645次。这个肯定是末尾的IDAT了,因为IDAT必须要满了才会开始一下个IDAT,这个明显就是末尾的IDAT了。,对应下面的create_head()代码。,对应下面的create_tail()代码。不要考虑爆破,我已经试了一下,太多情况了。题目来源:UNCTF。_攻防世界困难模式攻略图文
文章浏览阅读2.9k次,点赞3次,收藏10次。偶尔会用到,记录、分享。1. 数据库导出1.1 切换到dmdba用户su - dmdba1.2 进入达梦数据库安装路径的bin目录,执行导库操作 导出语句:./dexp cwy_init/[email protected]:5236 file=cwy_init.dmp log=cwy_init_exp.log 注释: cwy_init/init_123..._达梦数据库导入导出
文章浏览阅读1.9k次。1. 在官网上下载KindEditor文件,可以删掉不需要要到的jsp,asp,asp.net和php文件夹。接着把文件夹放到项目文件目录下。2. 修改html文件,在页面引入js文件:<script type="text/javascript" src="./kindeditor/kindeditor-all.js"></script><script type="text/javascript" src="./kindeditor/lang/zh-CN.js"_kindeditor.js
文章浏览阅读2.3k次,点赞6次,收藏14次。SPI的详情简介不必赘述。假设我们通过SPI发送0xAA,我们的数据线就会变为10101010,通过修改不同的内容,即可修改SPI中0和1的持续时间。比如0xF0即为前半周期为高电平,后半周期为低电平的状态。在SPI的通信模式中,CPHA配置会影响该实验,下图展示了不同采样位置的SPI时序图[1]。CPOL = 0,CPHA = 1:CLK空闲状态 = 低电平,数据在下降沿采样,并在上升沿移出CPOL = 0,CPHA = 0:CLK空闲状态 = 低电平,数据在上升沿采样,并在下降沿移出。_stm32g431cbu6
文章浏览阅读1.2k次,点赞2次,收藏8次。数据链路层习题自测问题1.数据链路(即逻辑链路)与链路(即物理链路)有何区别?“电路接通了”与”数据链路接通了”的区别何在?2.数据链路层中的链路控制包括哪些功能?试讨论数据链路层做成可靠的链路层有哪些优点和缺点。3.网络适配器的作用是什么?网络适配器工作在哪一层?4.数据链路层的三个基本问题(帧定界、透明传输和差错检测)为什么都必须加以解决?5.如果在数据链路层不进行帧定界,会发生什么问题?6.PPP协议的主要特点是什么?为什么PPP不使用帧的编号?PPP适用于什么情况?为什么PPP协议不_接收方收到链路层数据后,使用crc检验后,余数为0,说明链路层的传输时可靠传输
文章浏览阅读587次。软件测试工程师移民加拿大 无证移民,未受过软件工程师的教育(第1部分) (Undocumented Immigrant With No Education to Software Engineer(Part 1))Before I start, I want you to please bear with me on the way I write, I have very little gen...
文章浏览阅读304次。Thinkpad X250笔记本电脑,装的是FreeBSD,进入BIOS修改虚拟化配置(其后可能是误设置了安全开机),保存退出后系统无法启动,显示:secure boot failed ,把自己惊出一身冷汗,因为这台笔记本刚好还没开始做备份.....根据错误提示,到bios里面去找相关配置,在Security里面找到了Secure Boot选项,发现果然被设置为Enabled,将其修改为Disabled ,再开机,终于正常启动了。_安装完系统提示secureboot failure
文章浏览阅读10w+次,点赞93次,收藏352次。1、用strtok函数进行字符串分割原型: char *strtok(char *str, const char *delim);功能:分解字符串为一组字符串。参数说明:str为要分解的字符串,delim为分隔符字符串。返回值:从str开头开始的一个个被分割的串。当没有被分割的串时则返回NULL。其它:strtok函数线程不安全,可以使用strtok_r替代。示例://借助strtok实现split#include <string.h>#include <stdio.h&_c++ 字符串分割
文章浏览阅读2.3k次。1 .高斯日记 大数学家高斯有个好习惯:无论如何都要记日记。他的日记有个与众不同的地方,他从不注明年月日,而是用一个整数代替,比如:4210后来人们知道,那个整数就是日期,它表示那一天是高斯出生后的第几天。这或许也是个好习惯,它时时刻刻提醒着主人:日子又过去一天,还有多少时光可以用于浪费呢?高斯出生于:1777年4月30日。在高斯发现的一个重要定理的日记_2013年第四届c a组蓝桥杯省赛真题解答
文章浏览阅读851次,点赞17次,收藏22次。摘要:本文利用供需算法对核极限学习机(KELM)进行优化,并用于分类。
文章浏览阅读1.1k次。一、系统弱密码登录1、在kali上执行命令行telnet 192.168.26.1292、Login和password都输入msfadmin3、登录成功,进入系统4、测试如下:二、MySQL弱密码登录:1、在kali上执行mysql –h 192.168.26.129 –u root2、登录成功,进入MySQL系统3、测试效果:三、PostgreSQL弱密码登录1、在Kali上执行psql -h 192.168.26.129 –U post..._metasploitable2怎么进入
文章浏览阅读257次。本文将为初学者提供Python学习的详细指南,从Python的历史、基础语法和数据类型到面向对象编程、模块和库的使用。通过本文,您将能够掌握Python编程的核心概念,为今后的编程学习和实践打下坚实基础。_python人工智能开发从入门到精通pdf