Kodewick
第 29 课,共 30 课目录

学过 C 的人看这里

第 29 课,共 30 课 · 第 7 部分 · 再往前 · 约 15 分钟

C++ 是从 C 长出来的,大部分 C 代码拿到 C++ 编译器里也能编译。可「能编译」不等于「该这样写」:这门课前面讲的很多东西,就是用来代替 C 里那些容易出错的写法的。这一课把它们并排放在一起:先是 C 的写法(只是给你看,不运行),再是 C++ 的写法(可以运行)。没学过 C 也可以读,就当是复习。

一个小例子,C 里你会这样写:

1#include <stdio.h>2 3int main(void) {4    printf("Hello, %s! You are %d.\n", "Ana", 30);5    return 0;6}

C++ 里这样写:

main.cpp
1#include <iostream>2#include <string>3 4int main() {5    std::string name = "Ana";6    int age = 30;7    std::cout << "Hello, " << name << "! You are " << age << ".\n";8    return 0;9}
输出
Hello, Ana! You are 30.

printf 换成 cout

printf 靠格式符认类型:%d 配 int,%f 配 double。写错了(比如 %d 配上一个 double),编译器最多给个警告,运行时显示一个怪数。std::cout 自己知道每个值的类型,不需要格式符,也就不会配错。

要控制小数位和宽度,用第 3 课的 <iomanip>:

1printf("单价:%.2f\n", price);2printf("合计:%8.2f\n", price * count);
main.cpp
1#include <iomanip>2#include <iostream>3 4int main() {5    double price = 6.5;6    int count = 3;7    std::cout << std::fixed << std::setprecision(2);8    std::cout << "单价:" << price << '\n';9    std::cout << "合计:" << std::setw(8) << price * count << '\n';10    return 0;11}
输出
单价:6.50
合计:   19.50

%.2f 对应 std::fixed << std::setprecision(2),设一次,后面一直有效;%8.2f 里的 8 对应 std::setw(8),只管紧跟着的那一个值。

注意: C 的头文件在 C++ 里也能用,名字去掉 .h、前面加 c:<stdio.h> 是 <cstdio>,<math.h> 是 <cmath>,<string.h> 是 <cstring>。std::printf 照样能用,但一个程序里最好只选一种输出方式。

char 数组换成 std::string

C 的字符串是 char 数组加结尾的 '\0':要自己算好数组够不够大,复制用 strcpy,拼接用 strcat,比较用 strcmp,写错一个就越界。

1char full[32];2strcpy(full, "Ana");3strcat(full, " Silva");4if (strcmp(full, "Ana Silva") == 0) {5    printf("%s, %zu letters\n", full, strlen(full));6}

std::string 自己管内存,多长都放得下;复制是 =,拼接是 +,比较是 ==:

main.cpp
1#include <iostream>2#include <string>3 4int main() {5    std::string full = "Ana";6    full += " Silva";7    if (full == "Ana Silva") {8        std::cout << full << "," << full.size() << " 个字符\n";9    }10    std::string copy = full;11    copy[0] = 'E';12    std::cout << full << " / " << copy << '\n';13    return 0;14}
输出
Ana Silva,9 个字符
Ana Silva / Ena Silva

copy = full 是真的复制了一份,改 copy 不影响 full。在 C 里,两个 char* 之间写 = 只是让两个指针指着同一块内存。

先猜一猜

这段程序显示什么?

1#include <iostream>2#include <string>3 4int main() {5    std::string s = "ab";6    s += "cd";7    std::cout << s.size() << '\n';8    return 0;9}

数组换成 vector

C 的数组传给函数时,只传过去一个指针,长度得另外传:

1double average(const double *a, int n) {2    double total = 0;3    for (int i = 0; i < n; i++) total += a[i];4    return total / n;5}

std::vector 自己知道有几个元素,还能 push_back 变长:

main.cpp
1#include <iostream>2#include <vector>3 4double average(const std::vector<double>& temps) {5    double total = 0;6    for (double t : temps) {7        total += t;8    }9    return total / temps.size();10}11 12int main() {13    std::vector<double> temps{18.5, 21.0, 19.5};14    temps.push_back(23.0);15    std::cout << temps.size() << " 天,平均 " << average(temps) << " 度\n";16    return 0;17}
输出
4 天,平均 20.5 度

const std::vector<double>& 不复制、也不改,相当于 C 里的 const double * 加上长度。

malloc 和 free

C 里要一块大小运行时才知道的内存,就 malloc,用完 free。忘了 free 是泄漏,free 两次或者 free 以后还用,都是最难找的错。

1int n = 5;2int *squares = malloc(n * sizeof(int));3for (int i = 0; i < n; i++) squares[i] = i * i;4free(squares);

C++ 里,要一排元素就用 vector;要单独一个放在堆上的对象,用第 21 课的 std::unique_ptr。两者都会自己把内存还掉,不用写 free,也不用写 delete:

main.cpp
1#include <iostream>2#include <memory>3#include <string>4#include <vector>5 6struct Account {7    std::string owner;8    double balance;9};10 11int main() {12    int n = 5;13    std::vector<int> squares(n);14    for (int i = 0; i < n; i++) {15        squares[i] = i * i;16    }17    std::cout << "最后一个:" << squares[n - 1] << '\n';18    auto acc = std::make_unique<Account>();19    acc->owner = "小林";20    acc->balance = 120.5;21    std::cout << acc->owner << ":" << acc->balance << '\n';22    return 0;23}
输出
最后一个:16
小林:120.5

squares 和 acc 在 main 结束时自己把内存还掉。点单步,走到最后看堆上的块消失。C++ 里也有 malloc,但它只给一块生的内存,不调用构造函数,std::string 这样的成员放进去就坏了:在 C++ 里别用它。

struct、nullptr、bool

C 里用 struct 常常要先 typedef 一下,不然每次都得写 struct Point;空指针写 NULL;真假要么用 int,要么包含 <stdbool.h>。

1typedef struct {2    int x;3    int y;4} Point;5 6Point *p = NULL;7int found = 0;

C++ 里 struct 的名字直接就是类型名,不用 typedef;空指针写 nullptr;bool 是内置的类型:

main.cpp
1#include <iostream>2 3struct Point {4    int x;5    int y;6};7 8int main() {9    Point a{3, 4};10    Point* p = nullptr;11    bool found = false;12    if (p == nullptr) {13        p = &a;14        found = true;15    }16    std::cout << std::boolalpha << found << " " << p->x << "," << p->y << '\n';17    return 0;18}
输出
true 3,4

NULL 在 C++ 里其实是一个整数 0,传给重载的函数时,可能挑中参数是整数的那个版本,也可能干脆挑不出来;nullptr 只能当指针,不会弄混。

指针参数换成引用

C 里函数要改调用者的变量,只能传指针,调用时写 &,函数里到处写 *:

1void add_tax(double *price) {2    *price = *price * 1.1;3}4add_tax(&price);

C++ 用第 10 课的引用:函数里像用普通变量一样用,调用时也不用写 &:

main.cpp
1#include <iostream>2 3void add_tax(double& price) {4    price = price * 1.1;5}6 7int main() {8    double price = 100;9    add_tax(price);10    std::cout << "含税:" << price << '\n';11    return 0;12}
输出
含税:110

引用不会是空的,也不能改去指别的变量,所以比指针少了很多出错的地方。只读的大对象,用 const 引用传,比如 const std::string&。

还有这些不一样

CC++
(double)xstatic_cast<double>(x),一眼就看得出是转换
函数不能同名参数不同就能同名(重载,第 9 课)
#define MAX 100const int max_count = 100;,有类型
变量常写在开头用到时再声明,for (int i = 0; ...)
malloc 的结果直接赋给 int*C++ 里要转换,编译不过;所以更不该用 malloc

C++17 起还有更多(比如 std::string_view),这门课只用 C++14。

练一练

写好代码,点检查。提示随便看,实在想不出来再看答案。这三道都是把 C 的老习惯换成 C++ 的写法。

练习 1

不用 strcat 的全名

用 std::string 把 first 和 last 拼成全名(中间一个空格),显示全名和它的字符个数,像这样:Ana Silva(9 个字符)。

要显示
Ana Silva(9 个字符)
exercise1.cpp
1#include <iostream>2#include <string>3 4int main() {5    std::string first = "Ana";6    std::string last = "Silva";7 8    return 0;9}
输出
点「运行」或按 Ctrl+Enter
练习 2

修好它:引用不用写 &

这段程序还带着 C 的习惯,编译不过。改好它,让它显示 a=8 b=5。

要显示
a=8 b=5
exercise2.cpp
1#include <iostream>2 3void swap_values(int& a, int& b) {4    int t = a;5    a = b;6    b = t;7}8 9int main() {10    int a = 5;11    int b = 8;12    swap_values(&a, &b);13    std::cout << "a=" << a << " b=" << b << '\n';14    return 0;15}
输出
点「运行」或按 Ctrl+Enter
练习 3

不用 malloc 的一排数

n 是运行时才知道的个数。不用 malloc,用 std::vector<int> 放 1 到 n 每个数的 3 倍,再一行显示出来,空格隔开。

要显示
3 6 9 12 15 18 
exercise3.cpp
1#include <iostream>2#include <vector>3 4int main() {5    int n = 6;6 7    return 0;8}
输出
点「运行」或按 Ctrl+Enter

这一课学会了

  • printf 换成 std::cout:不用格式符,类型不会配错;小数位和宽度用 <iomanip>。
  • char[] 和 strcpy / strcat / strcmp 换成 std::string 的 =、+、==;size() 不算 '\0'。
  • 数组加长度换成 std::vector,传参用 const std::vector<T>&。
  • malloc / free 换成 vector 和 std::unique_ptr,内存自己还;C++ 里别用 malloc。
  • struct 不用 typedef;NULL 换成 nullptr;bool 是内置的。
  • 要改调用者的变量,传引用,不传指针;C 的头文件写成 <cstdio>、<cmath>。