本文使用 Zhihu On VSCode 创作并发布
先说什么呢
本系列的标题叫“C# 用户可以学的 C++ 微小子集”,实在是很绞尽脑汁想出来的东西。本来我还想写个“学会”,但转念一想,我也不敢担保我学“会”了这个微小子集。还有过别的想法,比如“C# 用户如何得到一个能用的 C++ 的微小子集”,可再仔细想想,“得到”这个词好像中文里意思不是那么明确,“能用”又不是很显然的(也许应该说“很显然是不能用的”),表意不明确。想来想去,就定了这么个标题。
其实想做这个东西也是因为暑假开始想学 C++ 玩,作为一个 C# 用户,学习期间很多时候都在想“这个东西在 C# 里面有等价物吗?”“这个东西和 C# 里面类似的东西有区别吗?”学着的感觉就像黑夜里从高塔上爬下来,一点一点试探着趋近大地。高级语言使用者来学 C++ 跟先学 C 再学 C++ 大概是完全不同的学习路径。
买的书是 C++ Primer,里面特别喜欢介绍标准库的东西。我就在想,既然玩高级语言的时候也都是直接库里的东西拿来用,为什么初学 C++ 就不行呢?先会用更容易用的东西,在此过程中熟悉一些基本概念,知道外面应该是个什么样子,再去搞里面的东西,对业余爱好者来说也不是不可以嘛。
总之呢,这几篇文章就是拿 C# 的东西跟 C++ 的东西做个对比。在对比中学习嘛。换句话说,就是给脑子里的 C# 东西找到一个可以映射到的 C++ 概念,这就是本文的主要目的。
作为业余爱好者,文章当中肯定会有不少错误,如果有任何问题或者写得不妥当的地方,敬请斧正。
正文
这是本系列的第一篇文章,所以先从最容易看到的地方讲起。
样子
我们先看 C# 的样子。
创建一个 C# 工程以后会有一个差不多这样的东西,大家都很熟悉。
using System;
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
}
这是我瞎写的,因为我也不记得里面应该是什么了。但我们可以看到,C# 里的函数就算看起来没有放在什么类里面的必要,外面也必须套个 class
,然后里面再把这些东西声明成 static
的。其实我觉得吧,这种没有必要有实例的东西叫它模块不就好了吗,非得搞个类做什么啊(
我们还可以看到开头有 using System;
。C# 里面我们要用什么 M$ 给的东西直接用(指 using 命名空间
)就好了。(要依赖别的项目另当别论啦)
然后我们看 C++ 的样子。
#include <iostream>
int main()
{
std::cout << "Hello World!" << "n";
return 0;
}
你看,外面没有套 class
,多干净。
你看,main
的返回值是 int
。
你看,最后还要返回个 0
。
你看,前面还有个 #include
。
等等,#include
是什么?
作为映射选手,这个系列没有必要搞明白那么多具体的东西。但作为 C# 人,#
这个符号总归是认识的吧。(不是说名字里的 #!)我相信你也写过 #if DEBUG
blah blah #endif
这种东西出来。
考虑到本系列优先只考虑这些东西表面上的作用,#include
表面上的作用和 C# 的 using
乍一看是相似的。共同点在于如果你没有 #include <iostream>
或者没有 using System;
,编译器都不知道你说的 std::cout
或者 Console
究竟是个啥。表面上的不同点就在于你就算不 using System;
你也可以靠写 System.Console
来用它,但是你要是不 #include <iostream>
你写再多个 std::
都没用。
等等,::
是什么?
我们都知道 System
是个命名空间,Console
是它下面的一个静态类。我们用点号来访问命名空间的一个成员。
类似的,std
也是个命名空间,虽然 cout
不是个类,(如果是类的话又怎么玩 <<
这种花头呢)但是的确是 std
下面的东西。而 C++ 跟 C# 不一样,C# 我们什么都点来点去,但是 C++ 不肯什么都点来点去,.
只能留给高贵的类的实例来用,要用命名空间或者访问类的静态成员只能用 ::
咯。
如果我不想写 std::
怎么办?
在 C# 里面我们 using
来 using
去的,using
完 System
从此再也不用写 System
。之前说 #include
差不多貌似相当于 using
显然是错的,因为我们 #include
完不是还要 std
来 std
去吗。
所以对标 C# using
的恰恰也应该是 using
,确切的说是 using namespace
。如果我们 using
了 namespace std
的话就再也不用 std
来 std
去了。唯一的问题就是仿佛我们 using
了它以后要是有人 #include
了我们的东西那不是人家也被迫 using
了吗。但是作为偷懒选手我们并不指望写出来什么很好的东西所以爱写也没关系。你看 C++ Primer 里的例子还不是通篇假设你已经 using
了嘛。偷懒乃人之常情。
沟通的艺术
人与人之间需要沟通,软件跟人之间也需要沟通。
其实之前我们已经间接地提到了了一部分输入输出的东西了。
作为 C# 用户,我们都很熟悉 Console.WriteLine
啦、Console.Write
啦,COnsole.ReadLine
啦,Console.ReadKey
这一套理论。
不过有些东西我们可能不太熟悉(C# 重度用户除外),比如 Console.Out
。
M$ Docs 告诉我们,Out
是它的一个属性,返回一个 TextWriter
对象,属性的作用叫:
获取标准输出流。
流,流……问君能有几多愁?恰似一江春水向东流。流是如此的常见,是很多人又爱又恨的东西。爱什么恨什么我们暂且不管,但我们返回来看看 C++ 那个 cout
的用法:
std::cout << "Hello World!" << "n";
啊!你看这美妙的 <<
,(零分描写示范警告)它就好像涓涓流水一样,把字符串无情的冲刷入 cout
的深渊!
如果你喜欢看正常内容,请看:
The global objects
std::cout
andstd::wcout
control output to a stream buffer of implementation-defined type (derived fromstd::streambuf
), associated with the standard C output streamstdout
.全局对象
std::cout
和std::wcout
控制输出到一个类型取决于实现的流缓冲区(派生自std::streambuf
),与标准 C 输出流stdout
相关联。(cppreference)
不要在意细节,大致看过去,我们知道了 cout
是个对象,它应该对标 Console.Out
才是。你说全局是什么?我也不知道,因为 C# 里没有全局变量。全局就是每个地方都能访问到的东西啦。
但探究具体是什么就和我们的主题矛盾了。要看怎么用。
C# 里,我们想用 Console.Write
写什么就写什么。
Console.Write("Bonjour!"); //字符串
Console.Write('n'); //字符
Console.Write(8.17); //浮点数
Console.Write(42); //整数
在 C++ 里,我们也想往 cout
里面些什么就写什么。
std::cout << "Bonjour!"; //字符串
std::cout << 'n'; //字符
std::cout << 8.17; //浮点数
std::cout << 42; //整数
你可能会说,你这个不对,我们 Write 系列明明可以顺便搞格式化字符串!
string name = "Trump";
Console.WriteLine("Hello, {0}", name);
//或者
Console.WriteLine($"Hello, {name}");
别忘了还有 <<
。你又不是只能 <<
一遍。你完全可以:
//需事先 #include <string>
std::string name = "Trump";
std::cout << "Hello, " << name << "n";
它不好用吗?(性能问题和本文主题无关)真的喜欢格式化字符串的话,出门右转 printf
不谢。
学会倾听。有输出必然有输入,我们在 C# 里面用 Console.ReadLine
和 Console.ReadKey
,虽然我感觉后者在绝大部分情况下的主要作用仿佛是“请按任意键继续...”,但这也差不多算倾听了吧。
比方说,我们在 C# 里读入一个名字,会这么写:
Console.Write("Enter your name: ");
string name = Console.ReadLine();
Console.WriteLine("Hello, {0}", name);
回到 C++,既然有 <<
就有 >>
。我们可以写:
//需事先 #include <string>
std::string name;
std::cout << "Enter your name: ";
std::cin >> name;
std::cout << "Hello, " << name << "n";
但是这个和 C# 版的行为是有差异的。时刻铭记,cin
的 >>
并不是读入一行的,而是读入一个词(以空白字符为分界)。甚至 >>
右边都可以不是一个字符串,比如:
//需事先 #include <string>
int number;
std::cout << "Enter a number: ";
std::cin >> number;
std::cout << "The number is, " << number << "n";
这个完全相同的效果 C# 不太好实现(也不是说不能实现,但是不是本文的主题了),我们也不想要这样的效果。我们还是希望能爽快地读进来一行字。那么我们就可以用 std
下面的一个函数 getline
。
std::string name;
std::cout << "Enter your name: ";
std::getline(std::cin, name);
std::cout << "Hello, " << name << "n";
现在它就可以读入完整的一行啦!
什么,你想要 ReadKey
的效果?如果你只是想要按任意键继续的话,我觉得 std::cin.get()
够用了。就千万别用它的返回值,超纲了。
本期完
悄悄说一句,其实 cin
有一个方法也叫 getline
,它和 std::getline
有什么区别呢?为什么我们用个字符串还要 #include
呢?我们下期再见!