欢迎光临散文网 会员登陆 & 注册

C++ 深入挖掘"Hello, world!"

2021-04-21 13:09 作者:刹那-Ksana-  | 我要投稿

前言

(该文技术性比较强,最初发布在CSDN上,后来做了删改后发到这里)

每个人学编程的时候,第一个接触的大概率都是Hello World,比如说Python:

再比如说,Java:

C++的版本则是:

B站代码块无法输入< >,所以用了数学符号里面的< >,以下代码都是这样

如果不算上 { } 的两行,这个代码一共只有3行,但是几乎没多少人会去一行行的详细挖掘其中的内容,所以今天我就准备更加深入的探索其中的每一行的细节。


1. Pre-processor

第一行:

是将iosteam这个库引入到我们的代码中,#include是一个所谓的pre-processor directives里面的一个(其他的还有#define, #pragma等等)。这个preprocess也是C、C++与众不同的一个地方。

如果你在某一行里面定义了preprocessor,那么你这一行里面不能再放其他非preprocessor的代码,但是preprocessor的语句可以横跨多行。preprocess末尾也不需要添加分号。

#include 我们知道有两种格式,一个是#include <library>,另一个是#include “library.h"。如果我们的library已经在我们的开发环境里面的话,我们用< >来导入,否则就用" "。如果你用的是Visual Studio的话,有关的设定可以在下图的地方看到

Property page

如果你的项目里面同一个library被include多次,那么你很可能在编译程序的时候会出错。所以很多情况下,我们用#pragma once解决重复include的问题。或者我们可以通过#ifndef#define这两个语句。(详细内容可参见:https://en.wikipedia.org/wiki/Include_guard)

每一个C++都有一个main函数:

在执行C++程序的时候,程序会自动找到main函数的所在位置,然后从main开始执行。

如果我们的程序里面没有main函数,绝大多数情况下,我们的程序不会被编译。(记得谁讲过,C++很热门的一个话题就是——Will it compile?)

这个main函数,还有另一个我们熟知的版本:

argv (Command line argument) 用来传输用户给的参数,因为argv是一个数组,所以我们需要argc (argument count) 来定义这个数组有多长。

此外,还有一个细节就是,这个main函数,需要返回一个int类型的数据,有时候我们也可以返回void,但编译器会返回如下的警告:

Warning C4326 return type of ‘main’ should be ‘int’ instead of ‘void’

但是这里我想说的其实是——注意我们的main函数下面没有return。一般而言,如果我们有一个函数需要返回int或者其他类型的数据,而我们没有return语句的话,编译器会返回如下的错误:

Error C4716 ‘Some Function’: must return a value

但是唯独main是一个特例,如果我们没有return语句的话,程序会自动帮我们return 0

最后,由于C++非Type safe,所以我们甚至可以返回一个Boolean类型或者char类型的数据,例如:

这个在Type safe的语言里面是不允许的。


接下来的一句:

我们都知道是输出Hello world到我们的屏幕上。看似简单,但是其中却包含着很多复杂的东西。

首先我们从最简单的开始说起。std是一个所谓的namespace

namespace的存在意义就是为了避免名字上面的冲突。比方说我手里有两个长得一模一样的两个函数,都叫做咖啡,那么这个时候我就用Starbucks和Dunkin’ Donuts将这两杯咖啡区分开来。(这里没有给这两家店做广告的意思 lol)

其次,cout来自于iostream这个库,所以我们必须要导入这个库,才能使用cout。而cout并不是唯一可以将内容输出到屏幕的方式,我们也可以用printf来替代cout

printf属于C里面的一个函数,而cout属于C++里面的函数,具体的性能差异可以忽略不计,printf还可以输出格式化的内容,如:

但cout却没有这个功能。此外,printf还有其他亲戚,如fprintf。除了printf以外,C++内还有puts,puts和printf比较接近。

以上三个方式的性能差异,都可以忽略不计,具体使用哪一个主要还是看每个程序员的习惯。

接下来,是<<这个符号,这个符号是将比特向左移动(bitwise left shift),a<<b就是将a的比特朝左移动b的距离。当然在这里,是运算符重载来赋予<<其他的意义——即将后面的那一串字符串print到我们的控制台里面。

此处运算符重载的格式为:

此处只列举float,其他参数还包括int, double, bool等十多种。这里留意这句语句的返回类型。

再接下来,一个非常复杂的问题是,cout到底是一个什么东西?

很明显,不像printf和puts,cout它不是一个函数(如果是函数那怎么能运算符重载呢?),它是一个std::ostream类型的object(ostream是一个class),并且这个实体名字叫做cout。

那么既然cout是一个实体的object,那么为什么我们不需要在代码里面创建实体,比如说:

这里值得一提的是,你的确可以创建一个std::ostream类型的实体,然后通过那个实体来执行ostream下某些功能:

但因为cout这个实体,我们的库已经帮我们创建好了,通过extern使得我们的cout成为一个全局可见的变量:

这样的话就剩得我们再去创建一个实体了。

接下来是ostream,它的原名叫做basic_ostream:

这里的using是给我们的某一个数据类型起一个别名,typedef能完成相同的任务:

这个basic_ostream的定义如下:

这里的_Elem里面放char type,比如说char, wchar之类的,而_Traits里面基本上是放std::char_traits<_Elem>(即,如果是char,那么就放std::char_traits<char>)。

而至于这个class本身,我目前还没有完全搞懂其中的机制。总之,它的作用就是操作所谓的流(stream),流由连续的byte组成,这个流可以是formatted(比如说int, boolean, 字符串类型的数据),也可以是unformatted(纯粹的binary数据),我们的"Hello, world"字符串就是一个流。

最后,如果我们要让cout输出一个自定义的内容的话,我们可以用以下的方式:

正如我们之前运算符重载那样,这里我们只是再额外重载一个,这里我们必须返回一个地址(reference),并且我们的参数里面必须传输一个ostream的地址,因为我们的cout是一个object。


后记

本来这个内容是打算在愚人节做成视频发出来博大家一乐的,后来发现这个真的是笑不出来


参考资料:

CPP Reference: https://en.cppreference.com/w/

Microsoft Docs C++ Language Reference: https://docs.microsoft.com/en-us/cpp/cpp/cpp-language-reference?view=msvc-160

THE END.

C++ 深入挖掘"Hello, world!"的评论 (共 条)

分享到微博请遵守国家法律