C++ 初步: 简化版

主要参考 cpp4python 和 “Learn C++: Programiz” app 上的小课, 内容是简化版中的简化版.

备用

C++ 文件的扩展名常见的有 .cpp, .cc 等.

Hello world

#include <iostream>

int main() {
    std::cout << "Hello World!" << std::endl;
    return 0;
}
  • 第一行可以写成 #include "iostream", 区别见 这里
  • 必须有 main 函数, 返回类型为 int, 约定 0 表示成功, 负数表示失败. 程序执行时隐式地调用该函数.
  • :: 相当于 Python 的 .
  • 在第二行加上 using namespace std; 之后下面可以直接用 cout (c stands for console)
  • endl 相当于 \n

Namespaces

namespace double_data {
    double num;
}

int main() {
    int num = 5;
    double_data::num = 2.33;
    return 0;
}

Atomic datatypes

int int1, int2 = 5;  // usually 4 bytes (32 bits)
// Note that `3/2` = 1 in cpp (`3//2` in python)

double d1 = 2.3, d2 = 2.3e6;  // preferred, 8 bytes
float d3 = 2.3f;  // 4 bytes

bool flag = true, flag1 = 1;  // 1 byte
// logical operators: &&, ||, !

char c = 'g';  // 1 byte, single quote
#include <string>
string s = "g";  // double quote

Pointers

// * 靠着变量名写, 更容易辨别 ptrN 是指针, varN 不是
// 从 code style 而言应该避免这么写
int *ptrN, varN;
varN = 100;

ptrN = &varN;  // get the address
cout << *ptrN;  // dereference

ptrN = nullptr;  // `NULL` for old cpp

Flow control

if (condition) {
    ...
}
else {
    ...
}

if 是函数, 没有 elif.

int i = 0;
while (i < 10) {
    ...
    i++
}

for (int i = 0; i < 10; i++) {
    ...
}

int nums[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// C++11
for (int n : nums) {
    cout << n << " ";
}

break, continue 照常.

Functions

void func(int num) {
    ...
}

这样写参数传值 (pass by value)

// 传引用
void swap(int &a, int &b) {
    int tmp; 
    tmp = a;
    a = b;
    b = tmp;
}

int main() {
    int a = 1, b = 2;
    swap(a, b);
    cout << a << ' ' << b;
}

传引用 (pass by reference) 调用后能改变原来的值. The actual location in memory referenced by the arguments are sent rather than the values in that location.

直接传指针也行.

// 传指针
void swap(int *a, int *b) {
    int tmp; 
    tmp = *a;
    *a = *b;
    *b = tmp;
}

int main() {
    int a = 1, b = 2;
    swap(&a, &b);
    cout << a << ' ' << b;
}

函数调用前必须有声明 (函数名, 输入输出类型, 称为 function prototype) 或者定义 (可以先声明, 再调用, 最后定义), 如

void func(int);

重载 (overload): 相同函数名, 不同输入输出类型.

Classes

class Animal {
    private:
        // data member
        int age = 5;

    // 不写默认是 private, 只有类内能访问
    // 另外还有 protected, 只有类内和子类能访问
    public:
        // common to all objects
        // declaration
        static string version;

        // member function
        void eat() {
            cout << "eating" << endl;
        }
        
        void set_age(int x) {
            age = x;  // 成员变量直接用
        }
        
        // can only use static variables inside the static function
        static void display_version() {
            cout << version;
        }
        
        virtual void foo() {
            cout << "类似抽象方法, 必须要子类覆盖";
        }
        
        // pure virtual function
        // 包含这种函数的才能叫抽象类
        // TODO: 那非纯的虚函数有什么存在必要?
        virtual void bar() = 0;
};  // 注意分号
// TODO: 为什么这里要分号?

// definition
string Animal::version;

class Dog : public Animal {  // TODO: public 作用?
    public:
        // function overriding
        void eat() {
            cout << "dog ";
            Animal::eat();
        }
        
        void foo() {
            cout << "wow";
        }
};

int main() {
    Animal animal;
    animal.eat();
    Animal::version = "2.3.3";
    
    Dog dog;
    dog.Animal::eat();  // 调用父类的方法
    
    Animal *animal2 = &dog;
    animal2->foo();  // 调用子类的方法

    return 0;
}
class Foo {
    public:
        int bar, a;
        // constructor, 相当于 Python 的 `__init__`
        Foo(int num, int num2) {
            bar = num;
            a = num2;
        }
        
        // 另一种写法
        // Fo
        -
        - o(int num, int num2) : bar(num), a(num2) {}
        // It is important to initalize data members
        // in the same order they are declared.
        // TODO
};

int main() {
    Foo foo(233);
    return 0;
}

友元函数感觉没啥用, 先略.

Collections

Arrays

int data[5] = {0, 1};  // 后三个未初始化
cout << data[0];
int data2[] = {0, 1};

/* 错误写法
* int data[5];
* data[5] = {0, 1};
*/

int data[2][2] = {
    {0, 1},
    {2, 3}
};

for (int num : data) {
    ...
}

数组与函数. A formal parameter for an array is neither a call-by-value nor a call-by-reference, but a new type of parameter pass called an array parameter. 在入参数组的方括号中写数字没有意义 (语法糖, 不论写几都一样), 入参不知道数组长度, 因此需要传入数组长度参数.

double average(int nums[], int length);

根据 index 取值时, cpp 不会检查 index 合法性 (提高性能), 所以可能取到不知道什么东西的位置, 导致错误.

存储方式比较特殊.

  • &x[0] 等价于 x (地址)
  • x[0] 等价于 *x (值)
  • x[1] 等价于 *(x + 1)

静态创建指编译时就分配好内存. 动态创建是运行时才决定.

int n;
cin >> n;

// allocate memory
int *ptr = new int[n];

// deallocate memory
delete[] ptr;

Vectors

Standard Template Library (STL) 中的数据结构.

更常用. Vectors use a dynamically allocated array to store their elements, so they can change size, and they have other friendly features as well.

#include <vector>
using namespace std;  // 下略

vector<int> nums = {0, 0, 0};
vector<int> nums2 {0, 0, 0};
vector<int> nums3(3, 0);  // 3 zeroes

int num = nums1.at(1);
// 相当于 num = nums[1] 但 at 有边界检查

双向链表

#include <list>

集合

TODO: 为什么集合有两种版本?

#include <set>
// 或者 #include <unordered_set>

Strings

#include <string>

string cpp_string = "c++ string is more modern";
char c_string[] = {"c string is an array of char with `\0` at the end"};

string str2 = "233"
cout << cpp_string + str2;
cpp_string.append(str2);  // 相当于 cpp string = cpp_string + str2

Hash tables

#include <map>
// 或者 #include <unordered_map>

map<int, string> int2string = {
    {1, "one"},
    {2, "two"}
};

int2string[1] = "uno";
int2string[3] = "three";

for (auto item : int2string) {
    cout << item.first << endl;
}

STL algorithms

#include <algorithm>

vector<int> nums = {0, 1, 2};
// 原地排序
// end 指向最后一个元素后一个的位置
sort(nums.begin(), nums.end());

File handling

#include <fstream>

fstream file_handle;

// `ios::out` writing mode, 会清空文件
// `ios::in` reading, `ios::app` append
// 模式可以写多个, 用 `|` 分隔
file_handle.open("sample.txt", ios::in);

if (file_handle.fail()) {
    cout << "File not found";
    exit(0);  // TODO: 写错了?
}

string line;
while (!file_handle.eof()) {
    // reads the file and stores it in `line`
    getline(file_handle, line);
    cout << line << endl;
    // 写入用 file_handle << "blahblah";
}

file_handle.close();

还有 ifstream, ofstream, 见 这里.

Exception handling

try {
    ...
}
// catch string literal
catch (const char *msg) {
    cout << msg;
}
catch (错误类型) {
    ...
}
// 写成 catch (...) 可以捕获所有 exception