c语言指针基础(中)

指针

assert断言

要想使用assert需要包含头文件<assert.h>,作用是程序在运行时要确定符合某种条件,如果符合程序正常运行,如果不符合,就会报错,停止运行。
例子:

int *p;
assert(p!=NULL)

程序运行到assert这条语句时,会判断p是不是空指针,如果不是空指针程序正常运行,时空指针程序报错停止运行。
assert接收的是一个表达式作为参数。如果该表达式为真(返回值!=0),assert不会起任何作用,程序还是正常运行。如果表达式为假(返回值=0),assert报错在标准错误 流 stderr 中写⼊⼀条错误信息,显⽰没有通过的表达式,以及包含这个表达式的⽂件名和⾏号。

#include<assert.h>
int main()
{
	int a = 0;
	int* p = &a;
	assert(p != NULL);
	{
		*p = 20;
		printf("%d ", *p);
	}
	
	return 0;
}

上面代码p明确指向了变量a,所以p不可能时空指针,所以程序正常运行,会将a的值改成20;
当p指向的是NULL时

#include<assert.h>
int main()
{
	int a = 0;
	int* p = NULL;
	assert(p != NULL);
	{
		*p = 20;
		printf("%d ", *p);
	}
	
	return 0;
}

在这里插入图片描述
他会告诉你在哪个文件的哪一行出现了错误。
assert()不仅仅可以用于指针,只要代码不符合你的预期时他都会产生作用。
比如:输入一个数,如果不是0就正常运行并且打印,如果是0,assert断言生效。

int main()
{
	int n = 0;
	scanf("%d", &n);
	assert(n != 0);//判断输入的值是不是0,不是0程序正常执行,是0系统报错程序停止运行。
	{
		printf("%d ", n);
	}
	return 0;
}

assert() 的使⽤对程序员是⾮常友好的,使⽤ assert() 有⼏个好处:它不仅能⾃动标识⽂件和 出问题的⾏号,还有⼀种⽆需更改代码就能开启或关闭 assert() 的机制。如果已经确认程序没有问 题,不需要再做断⾔,就在 #include <assert.h> 语句的前⾯,定义⼀个宏 NDEBUG 。

#define NDEBUG
#include<assert.h>

#define NDEBUG相当于assert的一个开关,当不确定程序有没有问题时就注释掉宏DEBUG,当确定程序没有问题时就在#include<assert.h>前加宏DEBUG
比如:

#define NDEBUG
#include<assert.h>
int main()
{
	int a = 0;
	int* p = &a;
	assert(p != NULL);
	{
		*p = 20;
		printf("%d ", *p);
	}
	
	return 0;
}

以上代码已经确定了程序没有问题这时就不需要assert来判断程序有没有问题了,这时就可以在#include<assert.h>前加宏DEBUG来使assert失效。

assert() 的缺点是,因为引⼊了额外的检查,增加了程序的运⾏时间。 ⼀般我们可以在 Debug 中使⽤,在 Release 版本中选择禁⽤ assert 就⾏,在 VS 这样的集成开 发环境中,在 Release 版本中,直接就是优化掉了。这样在debug版本写有利于程序员排查问题, 在 Release 版本不影响⽤⼾使⽤时程序的效率.

指针的使用和传址调用

模拟strlen

strlen是库函数,作用是统计字符串长度,在统计到’\0’时停止统计
自定义一个函数接收字符串的起始地址,然后统计’\0’之前字符的个数,只要不是\0计数器就++,然后将这个计数器存放的值返回。

size_t str_len(const char* p)//接收数组首元素地址,存放到指针变量p中,因为是要计算字符串个数不需要改变数组某个元素的值,所以可以在*前加count
{
	size_t count = 0;//计数器初始化为0
	assert(*p != NULL);//使用assert来判断p是不是空指针
	{
		while (*p != '\0')//当p指向的值不是\0时p++,计数器++
		{
			p++;
			count++;
		}
	}
	return count;
}
int main()
{
	char arr[] = "hello world";
	size_t len = str_len(arr);//传输数组首元素地址
	printf("%zd ", len);
}

传值调用和传址调用

传值调用

例如:写⼀个函数,交换两个整型变量的值
在没有学指针之前,写出来的代码可能会是:


void swap(int x, int y)
{
	int tmp = 0;
	tmp = x;
	x = y;
	y = tmp;
}

int main()
{
	int a = 10;
	int b = 20;
	printf("交换前:a=%d,b=%d\n", a, b);
	swap(a, b);
	printf("交换后:a=%d,b=%d\n", a, b);
	return 0;
}

在这里插入图片描述
这段代码虽然看起来没有太大的问题,也在swap函数中实现了两个值的交换,但最后打印上屏幕的却是没有交换时候的情况,原因如下。
因为在变量a,b创建时,会向内存申请空间假如a的地址是0x0011ff40,b的地址是0x0011ff44,程序进入到swap函数后由于x,y也是变量当作形参也会向内存申请空间,假如x的地址是:0x0011ff50,y的地址是0x0011ff54,所以会发现虽然将a,b的值传到了x,y中但a,x的地址不一样,b,y的地址不一样,函数swap虽然交换了,x,y的值但因为x,y是形参,形参的改变对实参没有作用,所以虽然x,y的值交换了,但a,b的地址没有进行交换,所以自然不会改变a,b本身的值。

总结成一句话:实参传递给形参时,形参会单独的创建一块临时空间来对形参进行接收,形参是实参的一份临时拷贝,对形参的改变不影响实参。

传址调用

要解决上面的问题就要想办法改变形参的同时也要相对应的改变实参,这时我们可以将a,b的地址传送给函数,然后交换两数的地址就可以形成两数的交换。

{
	int tmp = 0;
	tmp = *x;
	*x = *y;
	*y = tmp;
}

int main()
{
	int a = 10;
	int b = 20;
	printf("交换前:a=%d,b=%d\n", a, b);
	swap(&a, &b);
	printf("交换后:a=%d,b=%d\n", a, b);
	return 0;
}

这种通过地址改变两个数的值的操作叫传址调用
传址调⽤,可以让函数和主调函数之间建⽴真正的联系,在函数内部可以修改主调函数中的变量;所 以未来函数中只是需要主调函数中的变量值来实现计算,就可以采⽤传值调⽤。如果函数内部要修改 主调函数中的变量的值,就需要传址调⽤。

数组名的理解

数组名就是数组首元素的地址。以前写一个数组,要将数组首元素 的地址存起来要将数组首元素拿出来在将它存放到指针变量中。但是现在可以直接将数组名存放到指针变量。
证明:数组名就是数组首元素地址。


int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	printf("%x\n", &arr[0]);//打印数组首元素地址
	printf("%x\n", arr);//打印数组名地址
	return 0;
}

所以可以证明数组名就是数组首元素地址。
但是有两个例外,一个是sizeof(arr)和&arr,除此之外所有的数组名都是数组首元素。

sizeof(数组名)

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

如果说数组名就是数组首元素的地址的话那么上面代码计算的是数组第一个元素的大小应该是4/8字节,但结果不是这样的
在这里插入图片描述
可以看出最后打印的是40,这个40其实是整个数组的大小,所以在sizeof(数组名)的情况下计算的是整个数组的大小,这时数组名就不是数组首元素了。

&数组名

&数组名,这里的数组名表示的是整个数组,取出的是整个数组的地址

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

在这里插入图片描述
可以发现当&arr和&arr[0]都+1时所得到的数值不一样,&arr+1跳过了40个字节,跳过的是整个数组&arr[0]+1跳过了4个字节,跳过的是一个元素。

指针访问数组

当知道数组名是数组首元素地址时,这个时候就可以通过第一个元素的地址来访问后面的元素,这时可以打印出整个数组的内容

int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p = arr;//将数组首元素地址存放到指针变量p中
	int sz = sizeof(arr) / sizeof(arr[0]);//计算数组元素个数
	for (int i = 0;i < sz;i++)
	{
		scanf("%d", &arr[i]);
	}
	for (int i = 0;i < sz;i++)
	{
		printf("%d ", *(p + i));//知道第一个元素的地址后,可以遍历后面的元素
	}
	return 0;
}

由于是将arr存放到了p中所以arr是=p的,这时上面的arr和p都是可以互换的

int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* p = arr;//将数组首元素地址存放到指针变量p中
int sz = sizeof(arr) / sizeof(arr[0]);//计算数组元素个数
for (int i = 0;i < sz;i++)
{
	scanf("%d", &arr[i]);
}
for (int i = 0;i < sz;i++)
{
	printf("%d ", *(arr + i));//知道第一个元素的地址后,可以遍历后面的元素
}
return 0;

int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p = arr;//将数组首元素地址存放到指针变量p中
	int sz = sizeof(arr) / sizeof(arr[0]);//计算数组元素个数
	for (int i = 0;i < sz;i++)
	{
		scanf("%d", p+i);
	}
	for (int i = 0;i < sz;i++)
	{
		printf("%d ", p[i]);//知道第一个元素的地址后,可以遍历后面的元素
	}
	return 0;
}

所以arr[i]p[i](p+i)==(arr+i)

一维数组传参

前面都是在main函数中用sizeof计算数组元素个数,那将一个数组传给函数后,函数内部求的元素个数和在main函数中求得的元素个数一样吗?

void text  (int arr[])
	{
	int sz = sizeof(arr) / sizeof(arr[0]);
	printf("text函数中的sz=%d", sz);
	}
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	printf("main函数中的sz=%d\n", sz);
	text(arr);
	return 0;
}

发现在函数外部和函数内部计算的sz是不一样的
原因:在main函数中,sizeof(arr)计算的是整个数组的大小是40,sizeof(arr[0])是首元素的大小是4,所以最后是40/4=10
在text函数中虽然形参使用数组的形式接收实参的arr,但实参的arr即不是sizeof(arr),也不是&arr,所以他的本质是数组首元素的地址,所以形参的部分可以看成int * arr,arr就是数组首元素的地址,他的类型是int ,int是4字节,arr[0]也是4字节,所以就是4/4=1
⼀维数组传参,形参的部分可以写成数组的形式,也可以写成指针的形式。

冒泡排序

这时有一个数组

int arr[10]={10,9,8,7,6,5,4,3,2,1};

这个数组是倒序的,现在希望它以升序的方式进行排列;这时可以用到冒泡排序。
冒泡排序的核⼼思想就是:两两相邻的元素进⾏⽐较,如果不满足顺序就就交换,满足顺序就找下一对。

void maopao(int* p, int sz)
{
	for (int i = 0;i < sz - 1;i++)//数组要执行几趟排序
	{
		for (int j = 0;j < sz - i - 1;j++)//一趟排序要交换几次
		{
			if (p[j] > p[j + 1])//当前面的数比后面的数大的时候两个数就交换
			{
				int tmp = p[j];//实现两数的交换
				p[j] = p[j + 1];
				p[j + 1] = tmp;
			}
		}
	}
}
void print(int arr[], int sz)
{
	for (int i = 0;i < sz;i++)
	{
		printf("%d ", arr[i]);
	}
}
int main()
{
	int arr[10] = { 10,9,8,7,6,5,4,3,2,1 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	maopao(arr, sz);
	print(arr, sz);
	return 0;
}

上面的代码虽然可以对数组交换但是当数组有几个元素本身就是有序的时候,它还会进行计算这样增加了程序运行的速度,
比如:

int arr[10] = { 1,2,3,4,5,6,7,8,9,10};

解决方法.

void maopao(int* p, int sz)
{
	for (int i = 0;i < sz - 1;i++)//数组要执行几趟排序
	{
		int falg = 1;//假设数组本身是有序的
		for (int j = 0;j < sz - i - 1;j++)//一趟排序要交换几次
		{
			if (p[j] > p[j + 1])//当前面的数比后面的数大的时候两个数就交换
			{
				falg = 0;//一旦交换了就证明数组是无序的,这时将falg置为0
				int tmp = p[j];//实现两数的交换
				p[j] = p[j + 1];
				p[j + 1] = tmp;
			}
		}
		if (falg == 1)//falg==1证明数组有序,跳出本次循环,不进行任何操作
		{
			break;
		}
	}
}
void print(int arr[], int sz)
{
	for (int i = 0;i < sz;i++)
	{
		printf("%d ", arr[i]);
	}
}
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	maopao(arr, sz);
	print(arr, sz);
	return 0;
}

二级指针

二级指针的概念

指针变量存放的是变量的地址,那么指针变量的地址应该存放在哪里?

int main()
{
	int a = 10;
	int* p = &a;//p存放的是变量a的地址,p就是一个一级指针
	int** pa = &p;//pa存放的是指针p的地址,那么pa是一个二级指针
	return 0;
}

用画图的方式理解二级指针
在这里插入图片描述
二级指针*号的应用

	int a = 10;
	int* p = &a;
	int** pa = &p;
	return 0;

pa前面的那个号代表pa是一个指针变量,int后面的号代表pa指向的类型是int *

二级指针的解引用操作

想通过二级指针改变变量的值可以对二级指针变量进行两次解引用操作,进行一次解引用操作找到的是一级指针的地址,进行第二次解引用操作就找到了变量的地址。

int main()
{
	int a = 10;
	int* p = &a;
	int** pa = &p;
	**pa = 20;
	printf("%d ", a);
	return 0;
}

在这里插入图片描述

指针数组

我们知道整形数组是存放整形的数组,字符数组是存放字符的数组,那么指针数组也应该就是存放指针变量的数组。
在这里插入图片描述
所以指针数组的每个元素都是指针,指向了同一片区域。

指针数组模拟二维数组

int main()
{
	int arr1[] = { 1,2,3,4,5 };
	int arr2[] = { 1,3,5,7,9 };
	int arr3[] = { 0,2,4,6,8 };
	int* arr[3] = { arr1,arr2,arr3 };
	for (int i = 0;i < 3;i++)
	{
		for (int j = 0;j < 5;j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
	return 0;
}

在这里插入图片描述

arr[0]指向的是arr1,arr[1]指向的是arr2

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

LVS 负载均衡 - DR模式

一 . DR 模式 直接路由 1.介绍&#xff1a; 直接路由&#xff08;Direct Routing&#xff09;&#xff1a;简称 DR 模式&#xff0c;采用半开放式的网络结构&#xff0c;与 TUN 模式的结构类似&#xff0c;但各节点并不是分散在各地&#xff0c;而是与调度器位于同一个物…

曲线曲面 - 连续性, 坐标变换矩阵

连续性 有两种&#xff1a;参数连续性&#xff08;Parametric Continuity&#xff09;、几何连续性&#xff08;Geometric Continuity&#xff09;参数连续性&#xff1a; 零阶参数连续性&#xff0c;记为&#xff0c;指相邻两段曲线在结合点处具有相同的坐标 一阶参数连续性&…

css-通用样式按钮加号

1.实现 2.代码 html <div class"addF">&#xff0b;</div> css .addF{width:40px;font-size:25px;font-weight:600;background-color:rgb(64, 158, 255);text-align:center;color:white;height:34px;border-radius:3px;line-height:34px; }

Windows下 OracleXE_21 数据库的下载与安装

Oracle 数据库的下载与安装 数据库安装包下载数据库安装访问数据库进行测试Navicat连接数据库 1. 数据库安装包的下载 1.1 下载地址 Oracle Database Express Edition | Oracle 中国 1.2 点击“下载 Oracle Database XE”按钮&#xff0c;进去到下载页面&#xff08;选择对…

ES基础-ES优化

优化-硬件选择 Elasticsearch 的基础是 Lucene&#xff0c;所有的索引和文档数据是存储在本地的磁盘中 磁盘在现代服务器上通常都是瓶颈。Elasticsearch重度使用磁盘&#xff0c;你的磁盘能处理的吞吐量越大&#xff0c;你的节点就越稳定。这里有一些优化磁盘I/O的技巧&#x…

数据处理分类、数据仓库产生原因

个人看书学习心得及日常复习思考记录&#xff0c;个人随笔。 数据处理分类 操作型数据处理&#xff08;基础&#xff09; 操作型数据处理主要完成数据的收集、整理、存储、查询和增删改操作等&#xff0c;主要由一般工作人员和基层管理人员完成。 联机事务处理系统&#xff…

ELF 1技术贴|在NXP源码基础上适配开发板的按键功能

本次源代码适配是在NXP i.MX6ULL EVK评估板的Linux内核源代码&#xff08;特定版本号为Linux-imx_4.1.15&#xff09;的基础中展开的。 首要任务集中在对功能接口引脚配置的精细调整&#xff0c;确保其能无缝匹配至ELF 1开发板。接下来&#xff0c;我们将详细阐述适配过程中关…

Maven对项目构建过程中的每个步骤的详细介绍

1. 概述 Maven除了管理项目的依赖以外&#xff0c;还能对项目的构建过程进行管理。除了使用命令行以外&#xff0c;我们平时经常用IDEA图形化界面进行操作&#xff0c;如图所示&#xff1a; 本文将详细描述Maven对项目构建过程中的每一个阶段。 2. 构建过程 注意&#xff1…

垂直分表、分布式Id详细介绍、模板引擎FreeMarker、对象存储服务MinIO(黑马头条Day02)

目录 垂直分表 分布式ID 为什么需要分布式ID 分布式ID需要满足的条件 常见的分布式ID算法有哪些 项目中具体如何使用分布式ID 模板引擎FreeMarker freemarker简介 对象存储服务MinIO MinIO简介 MinIO的优点 本项目中使用的FreeMarker和MinIO示例 今天在学习黑马头…

短视频矩阵系统技术开发商--支持技术资质核验(自研独立saas框架开发)

短视频矩阵系统是一种能够帮助用户快速制作、发布和推广短视频的系统。 &#x1f347;&#x1f347;它通常包括以下部分&#xff1a; 短视频矩阵系统#短视频矩阵系统源头#短视频矩阵系统源头开发#短视频矩阵系统软件#短视频矩阵系统技术#2024互联网风口项目短视频矩阵系统带动…

ORACLE RAC反应卡顿时enq: SV - contention和latch: row cache objects的分析

某客户数据库系统使用ORACLE RAC 11G版本&#xff0c;两个节点。在上午8点钟之后&#xff0c;业务开始大量进行时&#xff0c;出现严重的卡顿问题&#xff1b;在工程师分析后&#xff0c;发现当时出现了很多异常等待数据&#xff0c;如典型的enq: SV - contention 、enq: TX - …

【Java JVM】Class 文件

Java 的口号 “一次编写, 到处运行 (Write Once, Run Anywhere)” 的基础: JVM 和 所有平台都统一支持的程序存储格式 – 字节码 (Byte Code)。 只要在对应的平台安装对应的 JVM, 将我们编写的源码编译为 Class 文件, 就能达到了一次编写, 导出运行的目标, 中间的所有细节由不同…

微服务基础

目录 一、单体架构 二、分布式架构 三、微服务 四、微服务结构 五、SpringCloud 六、服务拆分 七、远程调用 一、单体架构 单体架构就是将业务的所有功能都集中在一个项目中进行开发&#xff0c;并打成一个包进行部署。 他的优点很明显&#xff0c;就是架构简单&#xff…

微信小程序(五十二)开屏页面效果

注释很详细&#xff0c;直接上代码 上一篇 新增内容&#xff1a; 1.使用控件模拟开屏界面 2.倒计时逻辑 3.布局方法 4.TabBar隐藏复现 源码&#xff1a; components/openPage/openPage.wxml <view class"openPage-box"><image src"{{imagePath}}"…

单细胞联合BulkRNA分析思路|加个MR锦上添花,增强验证~

今天给大家分享一篇IF7.3的单细胞MR的文章&#xff0c;2023年12月发表在Frontiers in Immunology&#xff1a;An integrative analysis of single-cell and bulk transcriptome and bidirectional mendelian randomization analysis identified C1Q as a novel stimulated risk…

力扣刷题Days11第二题--141. 环形链表(js)

目录 1,题目 2&#xff0c;代码 2.1快慢指针 2.2&#xff0c;哈希表 3&#xff0c;学习与总结 3.1自己尝试写快慢指针 反思 1,题目 给你一个链表的头节点 head &#xff0c;判断链表中是否有环。 如果链表中有某个节点&#xff0c;可以通过连续跟踪 next 指针再次到达&…

【视频转码】基于RK3588的视频转码探索

传统的视频转码服务基本都是基于X86下CPU、GPU转码&#xff0c;对硬件性能、功耗、成本来说都比较高。从技术角度来说现有视频转码技术有&#xff1a; 视频编码转变&#xff1a; 1. H.264 > H.265 保持视频分辨率、清晰度不变情况下&#xff0c;更改视频压缩方式&#xff0…

hyperf 二十五 数据迁移 一

教程&#xff1a;Hyperf 版本说明 一 生成迁移 php bin/hyperf.php gen:migration create_users_table 执行文件&#xff1a;Hyperf\Database\Commands\Migrations\GenMigrateCommand 功能&#xff1a;创建迁移文件 参数&#xff1a; name 文件名称 选项&#xff1a; c…

【JS】关于this的使用

this 前言一、this是什么&#xff1f;二、做什么&#xff1f;1.全局环境2.函数环境3.new实例对象4.apply、bind、call绑定4.1 apply()4.2 call()4.3 bind() 三、为什么用this&#xff1f;四、如何改变this&#xff1f;五、应用场景&#xff1f;总结 前言 痛点 经常写Vue项目&a…

day36 贪心算法part5

435. 无重叠区间 中等 给定一个区间的集合 intervals &#xff0c;其中 intervals[i] [starti, endi] 。返回 需要移除区间的最小数量&#xff0c;使剩余区间互不重叠 。 气球问题稍加改动就可ac 一个交叉区间里&#xff0c;最终只能保留一个&#xff0c;其他的全部要去掉。…