旷世的忧伤

Huoty's Blog

C/C++ 札记:第一个 C++ 程序

C++ 是一种静态类型,编译型,强类型,通用的编程语言,支持面向过程和面向对象编程。C++ 是 C 的超集,是在 C 语言的基础上进一步扩充和完善,几乎任何合法的 C 程序都是合法的 C++ 程序。

第一个 C++ 程序:

/*
 * 第一个 C++ 程序
 */

/* 引入头文件 */
#include <iostream>

/* 引入名字空间 */
using namespace std;

/* 程序的入口函数 */
int main(void)
{
    // 定义变量,并赋值
    string s = "Hello world!";
    // 向标准流输出数据
    cout << s << endl;
    // 返回一个 int 类型的数据,用于表示程序的退出状态
    return 0;
}

可将以上代码保存在 hello.cpp 中,以编译运行程序。C++ 程序文件的后缀名通常用 .cpp.cc 或者 .cxx

注释

注释是解释性语句,不影响程序的逻辑,但能提高源代码的可读性,注释中的所有字符会被编译器忽略。

C++ 支持单行注释和多行注释。多行注释以 /* 开始,以 */ 结束。单行注释以 // 开始,直至行尾结束。多行注释也可以作为单行注释用。

/* 多行注释示例
第二行
第三行
*/

// 单行注释

/* 作为单行注释的多行注释 */

头文件

在编写 C++ 程序时,通常需要引入外部库(包括标准库和第三方库),这些外部库通常都以头文件和动态库或者静态库的新式提供。在 C++ 中,头文件也是一种源代码的形式,但头文件不用被编译。头文件主要提供全局变量、全局函数的声明或公用数据类型的定义,其作用主要是实现分离编译和代码复用。当需要外部的某些声明或者定义时,就可以将相应的头文件引入。引入头文件使用 #include 宏命令,宏会在编译器预处理阶段进行展开,#include 的作用就是把指定的文件的内容插入其所在位置处。

C++ 标准库的头文件通常不带 .h 后缀,这主要是为了与旧版或者 C 语言的头文件做区分,C++ 标准库的头文件通常可以在 /usr/include/c++ 中找到。

iostream 便是标准库中的头文件,其申明了 std::cout 和 std::endl 等变量。std::cout 表示输出到标准输出,std:endl 表示换行。

命名空间

命名空间(namespace) 主要用于区分不同库中相同名称的函数、类、变量等。使用了命名空间即定义了上下文。本质上,命名空间就是定义了一个范围。命名空间的声明使用 namespace 关键字,如:

namespace a {
    int c;
}

namespace b {
    int c;
}

int c;

使用名字空间需要使用 :: 域解析符,也叫做作用域操作符。如 a::cb::c,对于没有声明名字空间的全局空间,称为无名名字空间,访问时不需要指定域解析符,也可以用类似 ::c 的方式访问。

此外,还可以使用 using 关键字来引入名字空间。使用 using 时,可以引入名字空间中的单个标识符,也可以全部引入。如:

// 仅引入名字空间 std 中的 string, cout, endl
using std::string;
using std::cout;
using std::endl;

// 引入名字空间 std 中的所有内容
using namespace std;

这样声明之后,就如示例程序中一样,string、 cout、 endl 前面的 std:: 就可以省略不写了。但需要注意的是,虽然可以省略不写,但 string 变量的全名仍然是 std::string,如果有一个全局变量也叫 string,其与 std::string 应该是两个不同的变量。所以,为了区分,全局变量的 string 可以写作 ::string(如果 string 是函数,::string 引用的是无名名字空间中的 string,而如果是变量,则有无 :: 都为冲突)。

名字空间还可以嵌套,如:

namespace a {
    namespace b {
        int c = 1;
    }
}

int d = a::b::c;

分号

在 C++ 中,分号(;) 是一个语句结束符。每个语句必须以分号结束。也表明一个逻辑实体的结束。

在有些编程语言中,会以 换行符(\n) 作为结束符的标识。由于 c++ 不以换行符结束,所以可以在一行内放置多个语句,但是为了便于阅读,除非是某些特殊情况,通常都只在一行实现一个语句。

x = y; y = y + 1;  // 合法的语句

代码块

代码块是一组使用 大括号({}) 括起来的按物理逻辑连接的语句,也称为复合语句(compound statement)。一个代码块也标记着一个变量作用域。即在一个代码块中定义的变量,只在该代码块中有效。

{
    std::string s = "Hello world";
}

cout << s << endl;  // 此处的 s 未定义,会报错

程序入口

main() 函数是 C/C++ 程序的入口函数,所谓入口函数,是指程序从这里开始。实际上一个 C/C++ 程序也只执行 main 函数里的逻辑。一个 C/C++ 程序可以包含若干函数,但至少必须包含 main 函数。C/C++ 标准规定 main 函数的返回值类型为 int,返回值用于表示程序的退出状态,返回 0 表示程序正常退出,返回非 0,表示出现异常。C/C++ 标准规定,main 函数原型有两种:

/* 无参数形式 */
int main(void)
{
    ...
    return 0;
}

/* 带参数形式 */
int main(int argc, char *argv[])
{
    ...
    return 0;
}

int 指明了 main() 函数的返回值类型,函数名后面的圆括号一般包含传递给函数的信息。void 表示没有给函数传递参数。

函数

函数是执行某种操作的代码块。函数的定义需提供函数名称、返回值类型、参数列表,以及函数主体。函数定义的一般形式如下:

return_type function_name(parameter list)
{
   body of the function
}
  • 返回值类型: 定义函数是必须指定返回值类型,如 int、float、string 等。如果函数不需要返回值,则可以将返回值指定为 void,即表明函数不返回任何值
  • 函数名称: 为函数指定一个标识符,以便于后续用该名称使用函数。函数名和参数列表一起构成了函数签名
  • 参数: 参数就像是占位符。当函数被调用时,向参数传递一个值,这个值被称为实际参数。函数定义时指定的参数列表被称为形式参数。参数列表包括函数参数的类型、顺序、数量。参数是可选的,也就是说,函数可以不包含参数
  • 函数主体: 函数主体包含一组定义函数执行任务的语句,即描述函数要完成的功能

一个好的编程习惯是,将不同的逻辑任务划分到不同的函数中,依次来更好的组织,是代码逻辑清晰,更易读。

变量与类型

示例中的 string s = “hello world”; 表示定义 string 类型的变量,变量名为 s,值为 hello world。变量是标记内存区域的一个名称。定义变量 s 时,”hello world” 文件字符串被存放在内存中,s 则标记了这块内存的位置,访问 s 时则可以取出这块内存的内容。变量 s 是一个标识符,用于标记变量。

C++ 标识符 是用于标识变量,函数,类,模块或任何其他用户定义项的名称。标识符以字母 A 到 Z 或 a 到 z 或下划线 _开头,后跟零个或多个字母,下划线和数字(0到9)。C++ 对大小写敏感,即 A 和 a 是两个不同的标识符。

C++ 是强类型的语言,所以定义变量是必须指定变量的类型,类型决定了变量存储的大小和布局。C++ 中常见的变量类型有 char, bool, int, float, double 等。

int i = 1;
float f = 0.1;
double d = 0.01;

布尔类型(bool)相较于 C 语言是 C++ 中新增的类型,布尔值包括 true 或 false。如果布尔变量用于算术表达式中,将被隐式转换成 int 类型,true 为 1,false 为 0。

bool cond;
cond = true;
cout << cond << endl;  // output 1

此外,C++ 还包括 枚举、指针、数组、引用、数据结构、类等类型。

标准流

示例中的 cout « s « endl; 表示将变量 s 的内容输出到标准输出流中。标准流是一种 输入/输出功能,包括标准输入流、标准输出流和标准错误流。标准输入流连接输入设备,如键盘;标准输出流和标准错误流连接输出设备,如显示器、打印机等。

C++ 在 iostream 头文件中定义了 cin、cout、cerr 和 clog 对象,分别对应于标准输入流、标准输出流、非缓冲标准错误流和缓冲标准错误流。

<< 本来是移位运算符,在这里被 overload 成了输出函数。

示例类似如下一样 C 语言程序:

printf("Hello world!\n");

编译运行

编译 C++ 程序需要使用 g++ 编译器。最简单的编译方式:

g++ hello.cpp

编译时编译器默认将编译结果输出到 a.out 可执行文件中,可以直接运行该文件:

$ ./a.out
Hello world!

也使用 -o 选项指定输出的可执行程序的文件名。如:

g++ hello.cpp -o hello

如果有多个源文件,可以用如下的方式编译:

g++ test1.cpp test2.cpp -o test

在 unix 系统环境下,对于一些简单的测试代码,可以编写一个脚本来实现一键编译运行,如:

echo "========= Compile:"
if [[ "$*" =~ ".cpp" ]]; then
    CC="g++ -Wall -std=c++11"
    if [[ `uname -s` == "Darwin" ]]; then
        CC="${CC} -stdlib=libc++"
    fi
else
    CC="gcc -Wall"
fi

TMPDIR=$(dirname $(mktemp -u))
OUTFILE="${TMPDIR}/cplus-tmp-out"
if [[ -f $OUTFILE ]]; then
    /bin/rm -f $OUTFILE
fi

$CC "$@" -o $OUTFILE

if [[ $? == 0 ]]; then
    echo "========= Run:"
    $OUTFILE
fi

if [[ -f $OUTFILE ]]; then
    /bin/rm -f $OUTFILE
fi

如果程序比较简单,也可以尝试使用 cling,其是一个交互式的 C++ 解析器,基于 LLVM 和 C++ 的前端 clang。可以直接运行 cling 来测试简单的语法逻辑,如:

$ cling

****************** CLING ******************
* Type C++ code and press enter to run it *
*             Type .q to exit             *
*******************************************
[cling]$ int a = 1;
[cling]$ a
(int) 1
[cling]$ int b = 2;
[cling]$ b
(int) 2
[cling]$ a + b
(int) 3

也可以用 cling 直接运行一个文件:

$ cat hello.cpp | cling --nologo
Hello World

可以编写一个 shell function 来自动的判断是需要直接运行解释器,还是需要运行一个文件:

function kling() {
    if [ -f $1 ]; then
        cat $1 | cling --nologo
    else
        cling $*
    fi
}

参考资料

Top