【C++】特殊字符“0”,以及NULL相关
我们都知道,’\0’是字符串的结束标记。因此,执⾏这段代码:
#include<bits/stdc++.h>
using namespace std;
int main(){
cout<<"ab\0cd";
}
输出结果:ab
这是因为,cout默认判断字符串到结束符号\0,认为字符串结束了,因此就停⽌。
事实上,\0是⼀个⾮打印字符,也就是不能被打印出来的字符。如果直接尝试使⽤cout或者putchar输出\0,什么也不会发⽣。ascii码为0-31之间的字符都是⾮打印字符。
下⾯内容引⽤⾃《征服C指针》,是NULL相关内容。注意0对应的字符不是NULL,NULL表⽰空指针,不是字符。
(NULL是来⾃stdio的⼀个宏定义,⼀般是这样:#define NULL ((void*)0) )
补充NULL、0 和'\0'
经常有⼀种错误的程序写法:使⽤NULL来结束字符串。
/*通常,C 的字符串使⽤''结尾,可是因为strncpy()函数在 src 的长度⼤于len的情况下没有使⽤'\0'来结束,所以⼀板⼀眼地写了⼀个整理成C 的字符串形式的函数(企图)/
void my_strncpy(char dest, char src, int len) {
strncpy(dest, src, len);
dest[len] = NULL; ←使⽤NULL 来结束字符串!!
}
上⾯的代码,尽管在某些运⾏环境下能跑起来,但⽆论怎样它就是错误的。因为字符串是使⽤“空字符”
来结束的,⽽不是⽤空指针来结束。
在 C 语⾔标准中,空字符的定义为“所有的位为 0 的字节称为空字符(nullcharacter)”(5.2.1)。也就是说,空字符是值为 0 的字符。空字符在表现上通常使⽤'\0'。因为'\0'是常量,所以实际上它等同于 0。也许有些吓到你了,'\0'呀'a'呀什么的,它们的数据类型其实并不是char,⽽是int*。
* 如果是C++,就不是这个结论了。
另外,在我的环境中,NULL在 stdio.h ⾥的定义如下:
#define NULL 0看到这个,你可能会说:“说来说去,那还不都是 0 嘛。”确实在⼤部分的情况下是这样的,但背后的事情却异常复杂。正如前⾯说的那样,写成'\0'和写成常量的0其实是⼀样的。使⽤'\0'只不过是习惯使然。如果想让代码容易读,遵从习惯是⾮常重要的。
将0当作空指针来使⽤,除了极其例外的情况,通常是不会发⽣错误的。但是,如果在字符串的最后使⽤NULL,就必然会发⽣错误。标准允许将NULL定义成(void*)0,所以在NULL被定义成(void*)的时候,如果使⽤NULL来结束字符串,编译器必然会提⽰警告。
看到刚才的关于NULL的定义,可能有⼈会产⽣下⾯的推测:啥呀?所谓空指针,不就是为 0 的地址嘛。在 C 中,为 0 的地址上应该是不能保存有效数据的吧?放什么都起不到任何作⽤,这没什么⼤不
了的。这种推测好像颇有道理,但也是有问题的。确实在⼤多数的环境中,空指针就是为 0 的地址。但是,由于硬件状况等原因,世上也存在值不为 0 的空指针。偶尔会有⼈在获得⼀个结构体之后,先使⽤memset()将它的内存区域清零然后再使⽤。
printf怎么输出字符此外,虽然 C 语⾔提供了动态内存分配函数malloc()和calloc(),但是抱着“清零后⽐较好”的观点,偏爱 calloc()的⼈倒有很多。这样也许可以避免⼀些难以再现的bug。使⽤memset()和calloc()将内存区域清零,其实就是单纯地使⽤ 0 来填充位。通过这种处理,当结构体的成员中包含指针的时候,这个指针能不能作为空指针来使⽤,最终是由运⾏环境来决定的。顺便说⼀下,对于浮点数,即使它的位模式为 0,值也不⼀定为 0*。
* 整数类型还好,但是我还是感觉依赖环境编出来的代码是不⼲净的。
说到这⾥,哦,原来这样啊,所以要使⽤宏定义的NULL呢。对于空指针的值不为 0 的运⾏环境,NULL的值应该被#define成别的值吧。可能会有⼈产⽣以上的想法。实际上,这种想法也是有偏差的,这涉及问题的内部根源。
⽐如,尝试编译下⾯的代码:int *p = 3;在我的环境⾥,会出现以下警告:warning: initialization makes pointer from integer without a cast因为 3 ⽆论怎么说都是int型,指针和int型是不⼀样的,所以编译器会提⽰警告。尽管在我的环境⾥指针和int的长度都是 4 个字节,但还是出现了警告。如今的编
译器,⼏乎都是这样的。继续,让我们尝试编译下⾯的代码:int *p = 0;这⼀次没有警告。
如果说将int型的值赋予指针就会得到⼀个警告,那么为什么值为 3 的时候出现警告,值为 0 的时候却没有警告呢?简直匪夷所思!这是因为在 C 语⾔中,“当常量 0 处于应该作为指针使⽤的上下⽂中时,它就作为空指针使⽤”。上⾯的例⼦中,因为接受赋值的对象为指针,编译器根据上下⽂判断出“0应该作为指针使⽤”,所以将常数 0 作为空指针来读取。⽆论如何,编译器都会针对性地对待“需要将 0 作为指针进⾏处理的上下⽂”,所以即便是空指针的值不为 0 的情况下,使⽤常量 0 来代替空指针也是合法的。
此外,如上所述,有的环境中像下⾯这样定义NULL:#define NULL ((void*)0)
ANSI C 中,根据“应该将 0 作为指针进⾏处理的上下⽂”的原则,将常量 0 作为指针来处理。因此,显式将 0 强制转型成void*是没有意义的。但是在某些情况下,编译器也可能会理解不了“应该将 0 作为指针进⾏处理的上下⽂”。这些情况是:
没有原型声明的函数的参数
可变长参数函数中的可变部分的参数
ANSI C 中,因为引⼊了原型声明,只有在你确实做了原型声明的情况下,编译器才能知道你“想要传
递指针”。可是,对于以printf()为代表的可变长参数函数,其可变部分的参数的类型编译器是不能理解的。
另外糟糕的是,在可变长参数的函数中,还经常使⽤常量NULL来表⽰参数的结束(⽐如 UNIX 的系统调⽤execl()函数)。以上情况下,简单地传递常量 0,会降低程序的可移植性。因此,通过使⽤宏定义NULL来将 0 强制转型成void*,可以显式地告之编译器当前的0 为指针*。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。