程序诗篇里的灵动笔触:指针绘就数据的梦幻蓝图<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;
}