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

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

在这里插入图片描述

大家好啊,我是小象٩(๑òωó๑)۶
我的博客: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.kler.cn/a/528115.html

相关文章:

  • DeepSeek-R1论文研读:通过强化学习激励LLM中的推理能力
  • 课题介绍:水下惯性/地形组合导航精度提升的理论与方法研究
  • 本地部署DeepSeek
  • PyQt6医疗多模态大语言模型(MLLM)实用系统框架构建初探(上.文章部分)
  • 【Redis】hash 类型的介绍和常用命令
  • 【memgpt】letta 课程4:基于latta框架构建MemGpt代理并与之交互
  • 寒假刷题Day19
  • C#面试常考随笔8:using关键字有哪些用法?
  • 初识Cargo:Rust的强大构建工具与包管理器
  • DBUtils中QueryRunner(空参,传数据源)构造方法的区别及应用场景
  • 智能小区物业管理系统打造高效智能社区服务新生态
  • PHP Mail:高效邮件发送解决方案详解
  • AMS仿真方法
  • SQL进阶实战技巧:断点去重技术详解
  • 鸿蒙物流项目之基础结构
  • 解决Django非ORM模型提示初始化request问题
  • 文件读写操作
  • Sui 年度展望:2025 是走向主流的一年,将 Sui 打造成体验最友好的平台
  • DBeaver连接MySQL提示Access denied for user ‘‘@‘ip‘ (using password: YES)的解决方法
  • MySQL基础-多表查询
  • TensorFlow 示例摄氏度到华氏度的转换(二)
  • MySQL--》日志与主从复制的实战技巧
  • 【VM】VirtualBox安装ubuntu22.04虚拟机
  • 思考:从普通用户的角度而言,大模型选择的初步考量
  • p1044 栈
  • pytorch实现循环神经网络