1. 预备知识

1.1. 如何运行C++程序

  1. 使用文本编辑器编写程序,保存文件,这个文件就是源代码。

  2. 编译源代码。运行程序将源代码翻译为机器语言。翻译后的文件就是目标代码。

  3. 将目标代码与其他代码链接起来,生成程序的运行阶段版本。最终产品文件为可执行代码。

    链接指的是将目标代码同使用的函数的目标代码以及一些标准的启动代码组合起来,生成程序的运行阶段版本

编程步骤如下图:

编程步骤

从4.2版起,g++要求编译源代码文件时使用标记-std=c++0x: g++ -std=C++11 use_auto.cpp

2. 开始学习C++

2.1. 为什么main()不能使用其他名称?

C++程序必须包含一个名为main()的函数, 这是由系统的C++运行时决定的。编译器生成目标系统的可执行文件时,操作系统的启动入口就是C++运行时,然后运行时完成初始化之后就会调用main函数(入口点)。

存在一些例外情况:比如windows上编写DLL模块,是因为DLL模块不是独立的程序。

2.2. cin不支持录入空格

在C++中,用cin>>str;这种方法来接收字符串那么录入的str不能包含空格,否则它会按照空格将整个字符串切分成若干段。如果你要是想输入带空格的字符串那就要用到getline()这个函数了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>

using namespace std;

int main()
{
string name;
string address;
cout << "Please input your name: ";
getline(cin, name);
cout << "Please input your address: ";
getline(cin, address);
cout << "Your name is " << name << " and address is " << address << endl;
return 0;
}

执行程序:

1
2
3
4
5
6
(base) phoenine@EvandeMacBook-Pro C_Study_1 % g++ -std=c++17 cpp_chapter_2.cpp -o test
(base) phoenine@EvandeMacBook-Pro C_Study_1 % ./test
Please input your name: Evan Yang
Please input your address: Comba Street 64, cot
Your name is Evan Yang and address is Comba Street 64, cot
(base) phoenine@EvandeMacBook-Pro C_Study_1 %

3. 处理数据

3.1. 整型的范围

整型: char、short、int、long、long long(C++11)

C++提供了一种灵活的标准,确保了最小长度:

  • short至少16位
  • int至少与short一样长
  • long至少32位,且至少与int一样长
  • long long至少64位,且至少与long一样长

查看大小:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
#include <climits>

using namespace std;

int main()
{
int n_int = INT_MAX;
short n_short = SHRT_MAX;
long n_long = LONG_MAX;
long long n_llong = LLONG_MAX;

cout << "int is " << sizeof(int) << " bytes" << endl;
cout << "short is " << sizeof(short) << " bytes" << endl;
cout << "long is " << sizeof(long) << " bytes" << endl;
cout << "long long is " << sizeof(long long) << " bytes" << endl;

cout << "int is " << n_int << endl;
cout << "short is " << n_short << endl;
cout << "long is " << n_long << endl;
cout << "long long is " << n_llong << endl;
return 0
}

输出:

1
2
3
4
5
6
7
8
9
10
(base) phoenine@EvandeMacBook-Pro C_Study_1 % ./test                                  
int is 4 bytes
short is 2 bytes
long is 8 bytes
long long is 8 bytes
int is 2147483647
short is 32767
long is 9223372036854775807
long long is 9223372036854775807
(base) phoenine@EvandeMacBook-Pro C_Study_1 %

3.2. 整型的字面值

C++能以三种不同的计数方式来书写整数:

  • 基数为10
  • 基数为8(老式UNIX版本):0
  • 基数为16(硬件黑客的最爱): 0x或者0X
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>

using namespace std;

int main()
{
int chest = 42;
int waist = 42;
int inseam = 42;

cout << "Monsieur cuts a striking figure !\n";
cout << "chest = " << chest << " (decimal for 42)\n";
cout << hex;
cout << "waist = " << waist << " (hexadecimal for 42)\n";
cout << oct;
cout << "inseam = " << inseam << " (octal for 42)\n";
return 0;
}

输出:

1
2
3
4
5
(base) phoenine@EvandeMacBook-Pro C_Study_1 % ./test                                  
Monsieur cuts a striking figure !
chest = 42 (decimal for 42)
waist = 2a (hexadecimal for 42)
inseam = 52 (octal for 42)

3.3. 类型转换

C++11将使用大括号的初始化称为列表初始化(list-initialization),这种初始化常用于给复杂的数据类型提供值列表。列表初始化不允许缩窄(narrowing),即变量的类型可能无法表示赋给它的值。例如:

1
2
3
4
5
6
7
8
const int code = 66;
int x = 66;
char c1 {31325}; // narrowing, not allowed
char c2 {66}; //allowed because char can hold 66
char c3 {code}; //ditto
char c4 = {x}; // not allowed, cause x is not constant
x = 32325;
char c5 = x; //allowed by this form of initialization

编译:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
(base) phoenine@EvandeMacBook-Pro C_Study_1 % g++ -std=c++17 cpp_chapter_2.cpp -o test
cpp_chapter_2.cpp:9:14: error: constant expression evaluates to 31325 which cannot be narrowed to type 'char' [-Wc++11-narrowing]
char c1 {31325}; // narrowing, not allowed
^~~~~
cpp_chapter_2.cpp:9:14: note: insert an explicit cast to silence this issue
char c1 {31325}; // narrowing, not allowed
^~~~~
static_cast<char>( )
cpp_chapter_2.cpp:12:16: error: non-constant-expression cannot be narrowed from type 'int' to 'char' in initializer list [-Wc++11-narrowing]
char c4 = {x}; // not allowed, cause x is not constant
^
cpp_chapter_2.cpp:12:16: note: insert an explicit cast to silence this issue
char c4 = {x}; // not allowed, cause x is not constant
^
static_cast<char>( )
cpp_chapter_2.cpp:9:14: warning: implicit conversion from 'int' to 'char' changes value from 31325 to 93 [-Wconstant-conversion]
char c1 {31325}; // narrowing, not allowed
~^~~~~
1 warning and 2 errors generated.

3.4. auto声明

C++11新增一个工具,让编译器能够根据初始化值的类型推断变量的类型。重新定义了auto的含义。

auto n = 100 // n is int

auto x = 1.5; // x is double

auto y = 1.3e12L // y is long double

处理复杂类型,比如STL中的类型时,自动类型的优势才能显现出来:

1
2
std::vector<double> scores;
auto pv = scores.begin()

4. 复合类型

4.1. C++11数组初始化

初始化数组的时候,可以省略等号:

double earning[4] = {1.2e4, 1.1e4, 1.6e4, 1.7e4}等价于

double earning[4] {1.2e4, 1.1e4, 1.6e4, 1.7e4}

关于列表初始化的缩窄问题:

1
2
3
4
5
6
7
8
9
10
11
#include <iostream>

using namespace std;

int main()
{
long plifs[] = {25, 29, 3.0};
char slifs[4] = {'h', 'i', 1122011, '\0'};
char tlifs[4] = {'h', 'i', 112, '\0'};
return 0;
}

编译:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
(base) phoenine@EvandeMacBook-Pro C_Study_1 % g++ -std=c++17 cpp_chapter_2.cpp -o test
cpp_chapter_2.cpp:7:29: error: type 'double' cannot be narrowed to 'long' in initializer list [-Wc++11-narrowing]
long plifs[] = {25, 29, 3.0};
^~~
cpp_chapter_2.cpp:7:29: note: insert an explicit cast to silence this issue
long plifs[] = {25, 29, 3.0};
^~~
static_cast<long>( )
cpp_chapter_2.cpp:8:32: error: constant expression evaluates to 1122011 which cannot be narrowed to type 'char' [-Wc++11-narrowing]
char slifs[4] = {'h', 'i', 1122011, '\0'};
^~~~~~~
cpp_chapter_2.cpp:8:32: note: insert an explicit cast to silence this issue
char slifs[4] = {'h', 'i', 1122011, '\0'};
^~~~~~~
static_cast<char>( )
cpp_chapter_2.cpp:8:32: warning: implicit conversion from 'int' to 'char' changes value from 1122011 to -37 [-Wconstant-conversion]
char slifs[4] = {'h', 'i', 1122011, '\0'};
~ ^~~~~~~
1 warning and 2 errors generated.

4.2. 为什么使用cin.get()

istream类有一个get()成员函数。

使用不带任何参数的cin.get()可以读取下一个字符(即使是换行符), 因此可以处理换行符,比如:

1
2
3
cin.get(name, ArSize);     // read first line
cin.get(); // read newline
cin.get(dessert, ArSize); // read second line

另一种使用get()的方式是将两个类成员函数拼接起来:

cin.get(name, ArSize).get()

之所以可以这么做,是因为cin.get(name, ArSize)返回一个cin对象,该对象随后将被用来调用get()函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>

using namespace std;

int main()
{
const int ArSize = 20;
char name[ArSize];
char dessert[ArSize];

cout << "Enter your name: \n";
cin.get(name ,ArSize).get();
cout << "Enter your favorite dessert: \n";
cin.get(dessert, ArSize).get();
cout << "I have some delicious " << dessert;
cout << " for you, " << name << ".\n";
return 0;
}

输出:

1
2
3
4
5
6
7
(base) phoenine@EvandeMacBook-Pro C_Study_1 % g++ -std=c++17 cpp_chapter_2.cpp -o test
(base) phoenine@EvandeMacBook-Pro C_Study_1 % ./test
Enter your name:
Evan Yang
Enter your favorite dessert:
Chocolate Mouses
I have some delicious Chocolate Mouses for you, Evan Yang.

4.3. cin.clear()

当cin函数输入错误的时候,cin里面有个函数cin.rdstate()可以自动检测到输入错误,当cin.rdstate()返回0(即ios::goodbit)时表示无错误。若返回4则发生非致命错误,即ios::failbit,不能继续输入或操作。cin.clear()则可以控制此时cin里的标识。其中标识符号有:

  • goodbit 无错误
  • Eofbit 已到达文件尾
  • failbit 非致命的输入/输出错误,可挽回
  • badbit 致命的输入/输出错误,无法挽回。若在输入输出类里,需要加ios::标识符号

cin.clear()一般会与cin.sync()或者cin.ignore()一起使用。cin.sync()代表清除缓冲区中的未读信息, cin.ignore()代表缓冲区中指定个数的字符。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <iostream>

using namespace std;

int main()
{
int number = 0;
while(cin >> number,!cin.eof())
{
if(cin.bad())
throw runtime_error("IO stream corrupted");
if(cin.fail())
{

cout << "Error input: " << cin.rdstate() << endl;
cin.clear();
cin.ignore();
// cin.sync(); 这里用sync会死循环???为什么???
cout << "cin.rdstate(): " << cin.rdstate() << endl;
}
else
{
cout << "Input is: " << number << endl;
break;
}
}
return 0;
}

输出:

1
2
3
4
5
6
7
(base) phoenine@EvandeMacBook-Pro C_Study_1 % g++ -std=c++17 cpp_chapter_2.cpp -o test
(base) phoenine@EvandeMacBook-Pro C_Study_1 % ./test
s
Error input: 4
cin.rdstate(): 0
3
Input is: 3

5. 简单的文本输入输出

5.1. 写入文本文件

需要声明自己的ofstream对象并将其同文件关联起来,便可以像cout一样使用了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <iostream>
#include <fstream>

using namespace std;

const double DISCOUNT = 0.913;
int main()
{
char automobile[50];
int year;
double a_price;
double d_price;

ofstream outFile;
outFile.open("carinfo.txt");
cout << "Enter the mark and model of automobile: ";
cin.getline(automobile, 50);
cout << "Enter the model year: ";
cin >> year;
cout << "Enter the original asking price: ";
cin >> a_price;
d_price = DISCOUNT * a_price;
cout << fixed; //表示用一般的方式输出浮点数,而不是科学计数法
cout.precision(2); //表示浮点数的精度
cout.setf(ios_base::showpoint); //强制显示浮点数小数点后的0
cout << "Make the model: " << automobile << endl;
cout << "Year: " << year << endl;
cout << "Was asking: " << a_price << endl;
cout << "Now asking: " << d_price << endl;
outFile << fixed;
outFile.precision(2);
outFile.setf(ios_base::showpoint);
outFile << "Make the model: " << automobile << endl;
outFile << "Year: " << year << endl;
outFile << "Was asking: " << a_price << endl;
outFile << "Now asking: " << d_price << endl;
outFile.close();
return 0;
}

输出:

1
2
3
4
5
6
7
8
9
(base) phoenine@EvandeMacBook-Pro C_Study_1 % g++ -std=c++17 cpp_chapter_2.cpp -o test  
(base) phoenine@EvandeMacBook-Pro C_Study_1 % ./test
Enter the mark and model of automobile: Flitz Perky
Enter the model year: 2011
Enter the original asking price: 15600
Make the model: Flitz Perky
Year: 2011
Was asking: 15600.00
Now asking: 14242.80

此方式下文件不存在会新建,文件存在会覆盖。

5.2. 读取文本文件

通过方法is_open()来判断文件是否成功打开。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#include <iostream>
#include <fstream>

using namespace std;

const double DISCOUNT = 0.913;
const int SIZE = 50;

int main()
{
char fileName[SIZE];
ifstream inFile;
cout << "Enter name of data file: ";
cin.getline(fileName, SIZE);
inFile.open(fileName);
if (!inFile.is_open())
{
cout << "Could not open the file " << fileName << endl;
cout << "Program terminating.\n";
exit(EXIT_FAILURE);
}
double value;
double sum = 0.0;
double count = 0.0;
inFile >> value;
//方法good()指出最后一次读取输入的操作是否成功
while (inFile.good())
{
++count;
sum += value;
inFile >> value;
}
if (inFile.eof())
{
cout << "End of file reached.\n";
}
else if (inFile.fail())
{
cout << "Input terminted by data mismatch.\n";
}
else
{
cout << "Input terminated for unknown reason.\n";
}

if (0 == count)
{
cout << "No data processed.\n";
}
else
{
cout << "items read: " << count << endl;
cout << "Sum: " << sum << endl;
cout << "Average: " << sum / count << endl;
}
inFile.close();
return 0;
}

输出:

1
2
3
4
5
6
(base) phoenine@EvandeMacBook-Pro C_Study_1 % ./test                                  
Enter name of data file: carinfo.txt
End of file reached.
items read: 5
Sum: 38810.7
Average: 7762.14

6. 函数探幽

6.1. 内联函数

常规函数和内联函数之间的区别主要在于C++编译器如果将它们组合到程序中。内联函数的编译代码与其他程序代码内联起来了,即编译器将使用相应的函数代码替换函数调用。因此内联函数的速度比常规函数快,代价是占用更多的内存。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>

inline double square(double x) { return x * x;}

int main()
{
using namespace std;
double a, b;
double c = 13.0;
a = square(5.0);
b = square(a + 7.5);
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c square is: " << square(c++) << endl;
return 0;
}

输出:

1
2
3
4
5
(base) phoenine@EvandeMacBook-Pro C_Study_1 % g++ -std=c++17 cpp_chapter_4.cpp -o test                                                
(base) phoenine@EvandeMacBook-Pro C_Study_1 % ./test
a = 25
b = 1056.25
c square is: 169

输出表明,内联函数和常规函数一样,都是按值来传递参数的。

6.2. 引用变量

引用是已定义的变量的别名。引用变量的主要作用是作为函数的形参。通过将引用变量作为参数,函数将使用原始数据,这样除指针之外,引用也为函数处理大型结构提供了一种非常方便的途径。

值传递和引用传递:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <iostream>

double cube(double a);
double refcube(double &ra);

int main()
{
using namespace std;
double x = 3.0;
cout << cube(x);
cout << " = cube of " << x << endl;
cout << refcube(x);
cout << " = cube of " << x << endl;
return 0;
}

double cube(double a)
{
a *= a * a;
return a;
}

double refcube(double &ra)
{
ra *= ra * ra;
return ra;
}

输出:

1
2
3
4
(base) phoenine@EvandeMacBook-Pro C_Study_1 % g++ -std=c++17 cpp_chapter_4.cpp -o test
(base) phoenine@EvandeMacBook-Pro C_Study_1 % ./test
27 = cube of 3
27 = cube of 27

7. 命名空间

7.1. using声明和using编译指令

C++提供了两种机制(using声明和using编译指令)来简化对名称空间中名称的使用。using声明使特定的标识符可用,using编译指令使整个名称空间可用。比如:

1
2
3
4
5
6
7
8
9
10
11
namespace Jill {
double bucket(double n);
double fetch;
struct Hill {...};
}
char fetch;
int main(){
using Jill::fetch;
cin >> fetch; //read a value into Jill::fetch
cin >> ::fetch; //read a value into global fetch
}

using声明使一个名称可用,而using编译指令使所有的名称都可用。

1
using namespace Jill;

一般来说,使用using声明比使用using编译指令更安全。所以,建议将:

1
using namespace std;

修改为:

1
std::cout << ...

或者:

1
2
3
using std::cin;
using std::cout;
...

8. 对象和类