我们都知道,’\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()将它的内存区域清零然后再使用。

此外,虽然 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 为指针*。

* 关于这个话题,在 C 语言 FAQ(http://www.catnet.ne.jp/kouno/c_faq/c_faq.htm)中,也花费了一章的笔墨进行了讨论。

【C++】特殊字符“\0”,以及NULL相关的更多相关文章

  1. PHP中空字符串介绍0、null、empty和false之间的关系

    PHP中空字符串介绍0.null.empty和false之间的关系 作者: 字体:[增加 减小] 类型:转载 时间:2012-09-25   用PHP开发那么久,PHP中空字符串.0.null.emp ...

  2. 优先选择nullptr而不是0和NULL

    我们知道:0是一个int,而不是一个指针.如果C++在一个只有指针才能够使用的上下文中发现它只有一个0,那么它会勉强将0解释成空指针,但那时一种倒退行为.C++的主要方针是0就是一个int,而不是指针 ...

  3. item 8: 比起0和NULL更偏爱nullptr

    本文翻译自modern effective C++,由于水平有限,故无法保证翻译完全正确,欢迎指出错误.谢谢! 博客已经迁移到这里啦 先让我们看一些概念:字面上的0是一个int,不是一个指针.如果C+ ...

  4. php中0,空,null和false之间区别

    $a = 0; $b="0"; $c= ''; $d= null; $e = false; echo "5个变量-原始测试类型"; var_dump($a);/ ...

  5. 前台报错:Uncaught TypeError: Cannot read property '0' of null

    错误现象: var div1=mycss[0].style.backgroundColor;  //这一行提示360和chrome提示:Uncaught TypeError: Cannot read  ...

  6. C++中 0 与 NULL 与 nullptr之间的关系,nullptr_t 的实现

    C++中 0 与 NULL 与 nullptr之间的关系,nullptr_t 的实现 来源 http://blog.csdn.net/Virtual_Func/article/details/4975 ...

  7. js 中 0 和 null 、"" Boolean 值关系

    在做字符串非空判断时,无意发现一个问题,记录下以便以后回顾. 问题描述:非空判断,只是校验传值的内容是否为"".null .undefined.当变量 赋值的字符串内容为 0,此时 ...

  8. 【Mac + Appium】之运行报错:[UiAutomator] UiAutomator exited unexpectedly with code 0, signal null

    产生下面的原因是因为:与uiautomator2的weditor冲突,两者不能同时使用. 有时打开appium时会报错: [UiAutomator] UiAutomator exited unexpe ...

  9. php 0、null、empty和false之间的关系

    // 判断 0 与 ''.null.empty.false 之间的关系 $a = 0; echo "0 与 ''. empty.null.false 之间的关系:"; if($a ...

随机推荐

  1. 跟我一起写 Makefile(二)

    三.make是如何工作的 在默认的方式下,也就是我们只输入make命令.那么, 1.make会在当前目录下找名字叫"Makefile"或"makefile"的文 ...

  2. Java课程设计 ssm电影售票选座管理系统 电影网站的网页设计与制作mysql

    注意:此项目只截图部分功能,可评论区咨询查看项目全部功能演示 1.开发环境 开发语言:Java 后台框架:SSM(Spring+SpringMVC+Mybatis) 前端技术:HTML+CSS+Jav ...

  3. AI+云原生,把卫星遥感虐的死去活来

    摘要:遥感影像,作为地球自拍照,能够从更广阔的视角,为人们提供更多维度的辅助信息,来帮助人类感知自然资源.农林水利.交通灾害等多领域信息. 本文分享自华为云社区<AI+云原生,把卫星遥感虐的死去 ...

  4. (四)Linux之用户管理(用户和用户组)

    Linux之用户管理(用户和用户组) 目录 Linux之用户管理(用户和用户组) 一.概述 二.用户和组的关系 三.关于UID和GID(用户ID和组ID) 四.用户和组的数据 /etc/passwd内 ...

  5. (数据科学学习手札127)在Python中使用icecream实现高效debug

    本文示例代码及文件已上传至我的Github仓库https://github.com/CNFeffery/DataScienceStudyNotes 1 简介 尽管有那么多花里胡哨的debug工具和方式 ...

  6. 7、二进制安装K8s之部署kube-proxy

    二进制安装K8s之部署kube-proxy 1.创建配置文件 cat > /data/k8s/config/kube-proxy.conf << EOF KUBE_PROXY_OPT ...

  7. C#如何调用DOS命令

    在使用C#编辑过程中,通常需要利用外部命令来执行一些操作,从而完成特定的功能.下面小编就以利用C#调用DOS命令"Ver"显示系统版本号为例,给初学C#语言的网友讲解一下具体的调用 ...

  8. mfc HackerTools远程线程注入

    在一个进程中,调用CreateThread或CreateRemoteThreadEx函数,在另一个进程内创建一个线程(因为不在同一个进程中,所以叫做远程线程).创建的线程一般为Windows API函 ...

  9. 一些Java知识点

    1 import java.util.ArrayList; 2 3 public class Main { 4 5 public static void main(String[] args) { 6 ...

  10. Linux下MySQL主从复制(GTID)+读写分离(ProxySQL)-实施笔记

    GTID概念: GTID( Global Transaction Identifier)全局事务标识.GTID 是 5.6 版本引入的一个有关于主从复制的重大改进,相对于之前版本基于 Binlog 文 ...