程序诗篇里的灵动笔触:指针绘就数据的梦幻蓝图<3>

ops/2025/2/2 5:40:13/

在这里插入图片描述

大家好啊,我是小象٩(๑òωó๑)۶
我的博客:Xiao Xiangζั͡ޓއއ

很高兴见到大家,希望能够和大家一起交流学习,共同进步
在这里插入图片描述
今天我们来对上一节做一些小补充,了解学习一下assert断言,指针的使用和传址调用等…

目录

  • 一、assert 断言
  • 二、指针的使用和传址调用
    • 2.1 strlen的模拟实现
    • 2.2 传值调用和传址调用

一、assert 断言

assert 断言是一种在程序中用于检查某个条件是否为真的语句。它基于这样的假设:在程序的某个特定点上,某个条件应该始终成立。如果这个条件确实为真,程序将继续正常执行;但如果条件为假,断言就会失败,通常会导致程序抛出一个AssertionError 异常,从而中断程序的执行。这有助于快速定位程序中的错误,尤其是在开发和测试阶段。
assert.h 头文件定义了宏 assert() ,用于在运行时确保程序符合指定条件,如果不符合,就报错终止运行。这个宏常常被称为“断言”。

作用:
调试辅助:帮助开发者快速定位程序中的逻辑错误。例如,在一个计算平均值的函数中,可以使用断言来确保传入的列表不为空,因为计算空列表的平均值是没有意义的,这能让开发者在函数被错误调用时迅速发现问题。
代码契约:在编写函数或方法时,断言可以用于定义函数的前置条件和后置条件,形成一种代码契约。比如,一个除法函数,使用断言可以确保除数不为零这个前置条件,明确函数的使用规则,也让其他阅读和使用代码的人清楚函数的约束条件。
测试验证:在测试用例中,断言是验证测试结果是否符合预期的重要手段。比如在单元测试中,使用断言来检查函数的返回值是否等于预期值,或者检查某个对象的属性是否处于特定状态,从而判断测试是否通过。

assert(p != NULL);

上面代码在程序运行到这一行语句时,验证变量 p 是否等于 NULL 。如果确实不等于 NULL ,程序继续运行,否则就会终止运行,并且给出报错信息提示。
assert() 宏接受⼀个表达式作为参数。如果该表达式为真(返回值⾮零), assert() 不会产生任何作用,程序继续运行。如果该表达式为假(返回值为零), assert() 就会报错,在标准错误流 stderr 中写入一条错误信息,显示没有通过的表达式,以及包含这个表达式的文件名和行号。

assert() 的使用对程序员是非常友好的,使用assert()
有几个好处:它不仅能自动标识文件和出问题的行号,还有一种无需更改代码就能开启或关闭 assert()的机制。如果已经确认程序没有问题,不需要再做断言,就在 #include <assert.h> 语句的前面,定义⼀个宏 NDEBUG 。

#define NDEBUG
#include <assert.h>

然后,重新编译程序,编译器就会禁用文件中所有的 assert() 语句。如果程序又出现问题,可以移除这条 #define NDEBUG 指令(或者把它注释掉),再次编译,这样就重新启用了 assert() 语句。
assert() 的缺点是,因为引入了额外的检查,增加了程序的运行时间。
一般我们可以在 Debug 中使用,在 Release 版本中选择禁用 assert 就行,在 VS 这样的集成开发环境中,在 Release 版本中,直接就是优化掉了。这样在debug版本写有利于程序员排查问题,在 Release 版本不影响用户使用时程序的效率。

二、指针的使用和传址调用

2.1 strlen的模拟实现

库函数strlen的功能是求字符串长度,统计的是字符串中 \0 之前的字符的个数。
函数原型如下:

size_t strlen ( const char * str );

参数str接收一个字符串的起始地址,然后开始统计字符串中 \0 之前的字符个数,最终返回长度。
如果要模拟实现只要从起始地址开始向后逐个字符的遍历,只要不是 \0 字符,计数器就+1,这样直到 \0 就停止。
举个例子:

int my_strlen(const char* str)
{int count = 0;assert(str);while (*str){count++;str++;}return count;
}
int main()
{int len = my_strlen("abcdef");printf("%d\n", len);return 0;
}

2.2 传值调用和传址调用

学习指针的目的是使用指针解决问题,那什么问题,非指针不可呢?
例如:写一个函数,交换两个整型变量的值
我们可能写出这样的代码:

#include <stdio.h>
void Swap1(int x, int y)
{int tmp = x;x = y;y = tmp;
}
int main()
{int a = 0;int b = 0;scanf("%d %d", &a, &b);printf("交换前:a=%d b=%d\n", a, b);Swap1(a, b);printf("交换后:a=%d b=%d\n", a, b);return 0;
}

我们发现其实没产生交换的效果,这是为什么呢?
在这个代码里,Swap1 函数采用的是传值调用。传值调用的特点是,函数接收的是实参的副本,而不是实参本身。具体来说:
在 main 函数中调用 Swap1(a, b) 时,a 和 b 的值会被复制一份,分别传递给 Swap1 函数的形参 x 和 y。
在 Swap1 函数内部,虽然 x 和 y 的值进行了交换,但这只是对副本的操作,并不会影响到 main 函数中原始的 a 和 b 的值。

结论:实参传递给形参的时候,形参会单独创建一份临时空间来接收实参,对形参的修改不影响实参。
所以Swap1是失败的了。

那怎么办呢?
我们现在要解决的就是当调用Swap函数的时候,Swap函数内部操作的就是main函数中的a和b,直接将a和b的值交换了。那么就可以使用指针了,在main函数中将a和b的地址传递给Swap函数,Swap函数里边通过地址间接的操作main函数中的a和b,并达到交换的效果就好了。
像这样:

#include <stdio.h>
void Swap2(int* px, int* py)
{int tmp = 0;tmp = *px;*px = *py;*py = tmp;
}
int main()
{int a = 0;int b = 0;scanf("%d %d", &a, &b);printf("交换前:a=%d b=%d\n", a, b);Swap2(&a, &b);printf("交换后:a=%d b=%d\n", a, b);return 0;
}

我们可以看到实现成Swap2的方式,顺利完成了任务,这里调用Swap2函数的时候是将变量的地址传递给了函数,这种函数调用方式叫:传址调用。
传址调用,可以让函数和主调函数之间建立真正的联系,在函数内部可以修改主调函数中的变量;所以未来函数中只是需要主调函数中的变量值来实现计算,就可以采用传值调用。如果函数内部要修改主调函数中的变量的值,就需要传址调用。

好了,这节内容便结束了,附上这两节所写的代码:

#define _CRT_SECURE_NO_WARNINGS 
#include <stdio.h>//int main()
//{
//	int a = 10;
//	//int* pa = &a;//int*
//	//char* pc = &a;
//	void* pv2 = &a;
//	//*pv2;
//	//pv2++;
//
//	char ch = 'w';
//	//char*pc = &ch;
//	//int* pi = &ch;
//	void* pv = &ch;//char*
//	//*pv;//err
//	return 0;
//}//void test(void* pv)
//{
//	//pv在使用的时候,会强制类型转换,然后去使用
//	//
//}
//
//
//int main()
//{
//	int a = 10;
//	test(&a);
//	char c = 'w';
//	test(&c);
//	double d = 3.14;
//	test(&d);
//
//	return 0;
//}//int main()
//{
//	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
//	int i = 0;
//	int sz = sizeof(arr) / sizeof(arr[0]);//10
//	int* p = &arr[0];
//
//	//for (i = 0; i < sz; i++)
//	//{
//	//	*(p + i) = 0;
//	//}
//
//	for (i = 0; i < sz; i++)
//	{
//		scanf("%d", p + i);
//	}
//
//
//	for (i = 0; i < sz; i++)
//	{
//		printf("%d ", *(p + i));
//	}
//
//	return 0;
//}//int main()
//{
//	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
//	int i = 0;
//	int sz = sizeof(arr) / sizeof(arr[0]);//10
//	int* p = &arr[sz-1];
//
//	for (i = 0; i < sz; i++)
//	{
//		*(p - i) = i+1;
//	}
//
//	for (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 i = 0;
//	int sz = sizeof(arr) / sizeof(arr[0]);//10
//	int* p = &arr[sz - 1];
//
//	for (i = 0; i < sz; i++)
//	{
//		*p = i + 1;
//		p--;
//	}
//
//	for (i = 0; i < sz; i++)
//	{
//		printf("%d ", arr[i]);
//	}
//
//	return 0;
//}//int main()
//{
//	int arr[10] = { 0 };
//	int n = &arr[0] - &arr[9];
//	printf("%d\n", n);
//
//	return 0;
//}//int main()
//{
//	int arr[10] = { 0 };
//	char ch[5] = {0};
//	printf("%d\n", & arr[9] - &ch[0]);//err
//
//	return 0;
//}#include <string.h>
//strlen 是求字符串长度,统计的是字符串中\0之前的字符个数//int main()
//{
//	char arr[] = "abcdef";
//	//a b c d e f \0
//	//数组名其实是数组首元素的地址
//	//arr == &arr[0]
//	size_t len = strlen(arr);//6
//	printf("%zd\n", len);
//
//	return 0;
//}//size_t my_strlen(char* str)
//{
//	size_t count = 0;
//	while (*str != '\0')
//	{
//		count++;
//		str++;
//	}
//	return count;
//}
//
//size_t my_strlen(char* str)
//{
//	char* start = str;
//	while (*str != '\0')
//		str++;
//	return str - start;//指针-指针
//}
//
//
//int main()
//{
//	char arr[] = "abcdefghi";
//	//a b c d e f \0
//	//数组名其实是数组首元素的地址
//	//arr == &arr[0]-- char*
//	size_t len = my_strlen(arr);//6
//	printf("%zd\n", len);
//
//	return 0;
//}
////
//int main()
//{
//	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
//	int sz = sizeof(arr) / sizeof(arr[0]);
//	int* p = arr;
//	while (p < &arr[sz])
//	{
//		printf("%d ", *p);
//		p++;
//	}
//
//
//
//	//int i = 0;
//	//int* p = arr;//p = &arr[0];
//	//for (i = 0; i < sz; i++)
//	//{
//	//	printf("%d ", *(p + i));
//	//}
//
//
//	//for (i = 0; i < sz; i++)
//	//{
//	//	printf("%d ", arr[i]);
//	//}
//
//
//	return 0;
//}//const 是常属性-不能改变的属性//int main()
//{
//	const int a = 10;
//	//a变成了常变量,a的本质还是变量,但是因为被const修饰,所以不能改变
//	//a = 1;//err
//	int* p = &a;
//	*p = 1;
//
//	printf("%d\n", a);
//
//	return 0;
//}//int main()
//{
//	const int n = 10;
//	int arr[n];
//
//	return 0;
//}//int main()
//{
//	const int a = 10;
//	//a变成了常变量,a的本质还是变量,但是因为被const修饰,所以不能改变
//	//a = 1;//err
//	const int* p = &a;
//	*p = 1;
//
//	printf("%d\n", a);
//
//	return 0;
//}//const 修饰指针变量
//可以放在*的左边,也可以放在*的右边,意义是不一样的
//const 放在*的左边表示指针指向的内容,不能通过指针来改变了,但是指针变量本身是可以改变的
//const 放在*的右边表示指针变量本身不能被修改了,但是指针指向的内容是可以通过指针变量来改变的//int main()
//{
//	int a = 100;
//	int b = 1000;
//	int* const p = &a;
//	*p = 0;//ok
//	p = &b;//err
//
//	return 0;
//}//int main()
//{
//	int a = 100;
//	int b = 1000;
//	const int * p = &a;
//	//*p = 0;//err
//	p = &b;
//
//	return 0;
//}//int main()
//{
//	int a = 100;
//	int b = 1000;
//
//	const int* const p = &a;
//
//	return 0;
//}
//
//
//
////int main()
//{
//	int* p;//局部变量不初始化的时候,里边放的是随机值
//
//	*p = 20;//非法访问,p就是野指针
//
//	return 0;
//}//int main()
//{
//	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
//	int* p = arr;
//	int sz = sizeof(arr) / sizeof(arr[0]);
//	int i = 0;
//	for (i = 0; i <= sz; i++)
//	{
//		*p = i;
//		p++;
//	}
//
//	return 0;
//}
////
//int* test()
//{
//	int n = 100;
//	return &n;
//}
//
//int main()
//{
//	int* p = test();
//	printf("%d\n", *p);
//
//	return 0;
//}//
//int main()
//{
//	int* p = NULL;
//	if(p != NULL)
//		*p = 200;
//
//	return 0;
//}
//#define NDEBUG
#include <assert.h>//int main()
//{
//	int arr[10] = { 1,2,3,4,5 };
//	int* p = arr;
//	assert(p != NULL);
//	int i = 0;
//	for (i = 0; i < 5; i++)
//	{
//		printf("%d ", *p);
//		p++;
//	}
//	//
//	return 0;
//}//int main()
//{
//	int arr[10] = { 1,2,3,4,5 };
//	int* p = arr;
//	assert(p != NULL);
//	int i = 0;
//	for (i = 0; i < 5; i++)
//	{
//		printf("%d ", *p);
//		p++;
//	}
//	
//	return 0;
//}//int main()
//{
//	int a = 5;
//	assert(a != 5);
//
//	return 0;
//}
//
//size_t my_strlen(const char* str)
//{
//	size_t count = 0;
//	assert(str != NULL);
//
//	while (*str)//'\0' -- 0
//	{
//		count++;
//		str++;
//	}
//	return count;
//}
//
//int main()
//{
//	char arr[] = "abcdef";
//	size_t len =  my_strlen(arr);
//	printf("%zd\n", len);
//
//	return 0;
//}//void Swap1(int x, int y)
//{
//	int z = 0;
//	z = x;
//	x = y;
//	y = z;
//}
//
//int main()
//{
//	int a = 0;
//	int b = 0;
//	scanf("%d %d", &a, &b);
//	//写一个函数,交换a和b的内容
//
//	printf("交换前:a = %d b = %d\n", a, b);
//	Swap1(a, b);
//	printf("交换后:a = %d b = %d\n", a, b);
//	
//	return 0;
//}//int main()
//{
//	int a = 10;
//	int* p = &a;
//	*p = 20;
//	return 0;
//}//void Swap2(int* pa, int* pb)
//{
//	int z = 0;
//	z = *pa;//z = a
//	*pa = *pb;//a = b
//	*pb = z;//b = z
//}
//
//int main()
//{
//	int a = 0;
//	int b = 0;
//	scanf("%d %d", &a, &b);
//	//写一个函数,交换a和b的内容
//
//	printf("交换前:a = %d b = %d\n", a, b);
//	Swap2(&a, &b);
//	printf("交换后:a = %d b = %d\n", a, b);
//	
//	return 0;
//}//int Add(int x, int y)
//{
//	return x + y;
//}
//
//int main()
//{
//	int a = 3;
//	int b = 5;
//	int r = Add(a, b);//传值调用
//	printf("%d\n", r);
//	return 0;
//}//    3      5
int Max(int x, int y)
{return (x > y ? x : y);
}int main()
{int a = 3;int b = 5;int r = Max(a, b);//传值调用printf("%d\n", r);return 0;
}

http://www.ppmy.cn/ops/154952.html

相关文章

基于 STM32 的智能电梯控制系统

1. 引言 随着城市化进程的加速&#xff0c;高层建筑日益增多&#xff0c;电梯作为垂直交通工具的重要性愈发凸显。传统电梯控制系统在运行效率、安全性和智能化程度上已难以满足现代需求。智能电梯控制系统能够实时监测电梯的运行状态、乘客需求&#xff0c;并根据这些信息优化…

MusicFree-开源的第三方音乐在线播放和下载工具, 支持歌单导入[对标落雪音乐]

MusicFree 链接&#xff1a;https://pan.xunlei.com/s/VOI0RrVLTTWE9kkpt0U7ofGBA1?pwd4ei6#

Uber损失(Huber Loss):从均方误差到绝对误差的完美过渡

前言 在机器学习的世界里,损失函数就像是你在迷宫中的导航系统,它决定了你到底能否顺利找到出口,而出口的大小就代表着模型的表现。而在这么多的“导航系统”中,Huber损失(你可以叫它“Uber损失”,我觉得这名字挺有意思的,能不能打车到一个更好的模型呢?)凭借其独特的…

赛博算卦之周易六十四卦JAVA实现:六幺算尽天下事,梅花化解天下苦。

佬们过年好呀~新年第一篇博客让我们来场赛博算命吧&#xff01; 更多文章&#xff1a;个人主页 系列文章&#xff1a;JAVA专栏 欢迎各位大佬来访哦~互三必回&#xff01;&#xff01;&#xff01; 文章目录 #一、文化背景概述1.文化起源2.起卦步骤 #二、卦象解读#三、just do i…

【数据结构】空间复杂度

目录 一、引入空间复杂度的原因 二、空间复杂度的分析 ❥ 2.1 程序运行时内存大小 ~ 程序本身大小 ❥ 2.2 程序运行时内存大小 ~ 算法运行时内存大小 ❥ 2.3 算法运行时内存大小 ❥ 2.4 不考虑算法全部运行空间的原因 三、空间复杂度 ❥ 3.1空间复杂度的定义 ❥ 3.2 空…

Day49:添加字典元素

在 Python 中&#xff0c;字典是一个可变的数据类型&#xff0c;这意味着你可以随时添加新的键值对。今天我们将学习如何向字典中添加元素。 1. 使用方括号 ([]) 添加新元素 最简单的方法是通过字典的键&#xff0c;使用方括号 [] 来添加新的键值对。如果该键已经存在&#x…

kimi,天工,gpt,deepseek效果对比

偶然间碰到的这个问题&#xff0c;这个问题感觉有点意思&#xff0c;他不是定义性的问题&#xff0c;而是不同概念之间的区别对比&#xff0c;我觉得这个效果立竿见影&#xff0c;一看就能看出来回答问题水平的层次。 单纯这个问题的答案&#xff0c;deepseek远超gpt&#xff…

嵌入式八股文之深入理解 C语言中的指针相关概念

在 C 编程中,指针是一个强大且重要的概念,它允许我们直接操作内存地址。然而,指针相关的概念众多,如函数指针、指针函数、指针常量、常量指针、数组指针、指针数组以及野指针,这些概念容易混淆,却又在不同的编程场景中发挥着关键作用。本文将深入剖析这些概念,帮助读者更…