Holler,各位!今天我们继续给大家介绍指针相关的内容!废话不多说,我们直接进入正题
1. assert断⾔
assert.h
头⽂件定义了宏
assert()
,⽤于在运⾏时确保程序符合指定条件,如果不符合,就报
错终⽌运⾏。这个宏常常被称为“断⾔”。
1
assert(p !=
NULL
);
上⾯代码在程序运⾏到这⼀⾏语句时,验证变量
p
是否等于
NULL
。如果确实不等于
NULL
,程序
继续运⾏,否则就会终⽌运⾏,并且给出报错信息提⽰。
assert()
宏接受⼀个表达式作为参数。如果该表达式为真(返回值⾮零),
assert()
不会产⽣
任何作⽤,程序继续运⾏。如果该表达式为假(返回值为零),
assert()
就会报错,在标准错误
流
stderr
中写⼊⼀条错误信息,显⽰没有通过的表达式,以及包含这个表达式的⽂件名和⾏号。
assert()
的使⽤对程序员是⾮常友好的,使⽤
assert()
有⼏个好处:它不仅能⾃动标识⽂件和
出问题的⾏号,还有⼀种⽆需更改代码就能开启或关闭
assert()
的机制。如果已经确认程序没有问
题,不需要再做断⾔,就在
#include <assert.h>
语句的前⾯,定义⼀个宏
NDEBUG
。
#
define
NDEBUG
#
include
<assert.h>
然后,重新编译程序,编译器就会禁⽤⽂件中所有的
assert()
语句。如果程序⼜出现问题,可以移
除这条
#define NDBUG
指令(或者把它注释掉),再次编译,这样就重新启⽤了
assert()
语
句。
assert()
的缺点是,因为引⼊了额外的检查,增加了程序的运⾏时间。 ⼀般我们可以在 Debug
中使⽤,在
Release
版本中选择禁⽤
assert
就⾏,在
VS
这样的集成开 发环境中,在 Release
版本中,直接就是优化掉了。这样在debug版本写有利于程序员排查问题, 在 Release
版本不影响⽤⼾使⽤时程序的效率。
2. 指针的使⽤和传址调⽤
2.1strlen函数的模拟实现
我们知道strlen函数是用来测量一个字符串的长度大小的。strlen函数中直到查找到‘\0‘后便停止查找,我们可以进行strlen函数的模拟实现。
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 传值调⽤和传址调⽤
在我们写代码的过程中有些题目是只能通过指针来解决的。
例如:写⼀个函数,交换两个整型变量的值
void swap(int a, int b)
{
int tmp = a;
a = b;
b = tmp;
}
我们很可能写出这样一个代码,实际上这个代码不能完成题目的要求的,也就是并没有完成这两个数之间交换的完成。
通过对带妈妈的运行
我们发现在main函数内部,创建了a和b,a的地址是0x00cffdd0,b的地址是0x00cffdc4,在调⽤ Swap1函数时,将a和b传递给了Swap1函数,在Swap1函数内部创建了形参x和y接收a和b的值,但是 x的地址是0x00cffcec,y的地址是0x00cffcf0,x和y确实接收到了a和b的值,不过x的地址和a的地址不 ⼀样,y的地址和b的地址不⼀样,相当于x和y是独⽴的空间,那么在Swap1函数内部交换x和y的值, ⾃然不会影响a和b,当Swap1函数调⽤结束后回到main函数a和b的没法交换。Swap1函数在使⽤ 的时候,是把变量本⾝直接传递给了函数,这种调⽤函数的⽅式我们之前在函数的时候就知道了,这 种叫传值调⽤。
那么我们该如何实现一个函数来满足题目的要求呢?那么我们就要利用到指针了,进行传址调用。
void Swap2(int*px, int*py)
{
int tmp = 0;
tmp = *px;
*px = *py;
*py = tmp;
}
我们可以看到实现成Swap2的⽅式,顺利完成了任务,这⾥调⽤Swap2函数的时候是将变量的地址传 递给了函数,这种函数调⽤⽅式叫:传址调⽤。
传址调⽤,可以让函数和主调函数之间建⽴真正的联系,在函数内部可以修改主调函数中的变量;所 以未来函数中只是需要主调函数中的变量值来实现计算,就可以采⽤传值调⽤。如果函数内部要改 主调函数中的变量的值,就需要传址调⽤
3. 使⽤指针访问数组
我们可以用指针来访问变量,那么当然也可以利用指针来进行对数组的访问。
#include <stdio.h>
int main()
{
int arr[10] = {0};
//输⼊
int i = 0;
int sz = sizeof(arr)/sizeof(arr[0]);
//输⼊
int* p = arr;
for(i=0; i<sz; i++)
{
scanf("%d", p+i);
//scanf("%d", arr+i);//也可以这样写
}
//输出
for(i=0; i<sz; i++)
{
printf("%d ", *(p+i));
}
return 0;
}
这个代码搞明⽩后,我们再试⼀下,如果我们再分析⼀下,数组名arr是数组⾸元素的地址,可以赋值 给p,其实数组名arr和p在这⾥是等价的。那我们可以使⽤arr[i]可以访问数组的元素,那p[i]也是可以访问数组的
4. ⼆级指针和多级指针
指针变量也是变量,是变量就有地址,那指针变量的地址存放在哪⾥? 自然是放到二级指针里
对于⼆级指针的运算有:
•
*ppa
通过对ppa中的地址进⾏解引⽤,这样找到的是
pa
,
*ppa
其实访问的就是
pa
.
int
b =
20
;
*ppa = &b;
//
等价于
pa = &b;
•
**ppa
先通过
*ppa
找到
pa
,然后对
pa
进⾏解引⽤操作:
*pa
,那找到的是
a
.
**ppa =
30
;
//
等价于
*pa = 30;
//
等价于
a = 30;
5. 指针数组
指针数组顾名思义就是储存指针的数组, 属于数组的一种
指针数组和上述int, char类型的指针是一样的只不过,数组中储存的元素个数是指针类型而已。
6. 指针数组模拟⼆维数组
#include <stdio.h>
int main()
{
int arr1[] = {1,2,3,4,5};
int arr2[] = {2,3,4,5,6};
int arr3[] = {3,4,5,6,7};
//数组名是数组⾸元素的地址,类型是int*的,就可以存放在parr数组中
int* parr[3] = {arr1, arr2, arr3};
int i = 0;
int j = 0;
for(i=0; i<3; i++)
{
for(j=0; j<5; j++)
{
printf("%d ", parr[i][j]);
}
printf("\n");
}
return0;
}
parr[i]是访问parr数组的元素,parr[i]找到的数组元素指向了整型⼀维数组,parr[i][j]就是整型⼀维数 组中的元素。
上述的代码模拟出⼆维数组的效果,实际上并⾮完全是⼆维数组,因为每⼀⾏并⾮是连续的。
今天的内容到此结束啦,给位下一篇再见啦!!!🌹🌹🌹🌹🌹