原书:Essential C++, Stanley B. Lippman, 电子工业出版社, 2013.
章节:第1章 C++编程基础
环境:CLion + MinGW
写在前面:最近看了《C++ Primer》和《Essential C++》的前几章,就这一小部分内容来说,两者都要求一定的编程语言基础,前者的特点是非常全面且按部就班地介绍C++语法规则,而后者以具体案例引导,迅速介绍了常用的各种基本内容(类与对象、表达式、流程控制、vector对象、文件读写等)
总的来说,《Essential C++》更适合具有C语言基础,并迫切希望迅速上手C++的读者阅读
1.1 如何撰写C++程序
-
函数
(1) 由四部分组成:函数名、返回类型、参数列表、函数体
(2) main函数返回类型为int,习惯上当程序正确执行时返回0,程序执行出错时返回非零值
(3) main并非C++定义的关键字,但编译器将从main()函数开始执行程序
-
类
(1) 类是用户自定义的数据类型
(2) 数据的输入输出并非C++程序语言本身定义的一部分,而是由C++的一套面向对象的类层次体系提供支持,并作为C++标准库的一员
(3) 面向对象的类层次体系定义了整个家族体系的各相关类型,如终端与文件输入设备、终端与文件输出设备
-
string类
#include <iostream> #include <string> using namespace std; int main(){ string name; cin >> name; cout << "Hello" << name << endl; return 0; }
-
using namespace std
(1) using和namespace都是C++中的关键字,std是标准库所驻之命名空间的名称,标准库所提供的任何事物都封装在命名空间std内
(2) 命名空间(namespace)是一种将库名称封装起来的方法,可以避免和应用程序发生命名冲突的问题
1.2 对象的定义与初始化
-
构造函数语法
int num_tries(0); // int num_tries = 0;
构造函数初始化语法可用于多指初始化
#include <complex> complex<double> purei(0, 1); // 定义复数0 + 1i
-
转义字符
'\n' 换行符 '\t' 制表符 '\0' null '\'' 单引号 '\"' 双引号 '\\' 反斜线
1.3 表达式
-
算术运算符:+ - * / %
-
条件运算符
int cnt = 1; const int lineSize = 8; cout << (cnt % lineSize ? ' ' : '\n'); // 如果余数运算结果不为零,结果为' ',反之为'\n'
-
复合赋值运算符:+= -= *= /= %=
-
递增运算符和递减运算符:++ –
(1) 前置形式
int cnt = 0; cout << ++cnt;
原值先递增或递减,再被拿来使用,故输出1
(2) 后置形式
int cnt = 0; cout << cnt++;
原值先被使用,再递增或递减,故输出0
-
关系运算符:== != < > <= >=
-
逻辑运算符:|| && !
服从短路求值法,若表达式的值已确定便不再对剩下的条件表达式求值
bool condition1 = true, condition2 = false; if(condition1 || condition2){ // 由condition1为真可确定结果为真, 无需对condition2求值 } if(condition2 && condition1){ // 由condition2非真可确定结果非真, 无需对condition1求值 }
-
运算符的优先级(从高到低)
逻辑运算符NOT
算术运算符(*, /, %)
算术运算符(+, -)
关系运算符(<, >, <=, >=)
关系运算符(==, !=)
逻辑运算符AND
逻辑运算符OR
赋值运算符
1.4 条件语句和循环语句
-
switch语句
switch(num_tries){ case 1: cout << "Oops! Nice guess but not quite it" << endl; break; case 2: cout << "Hmm. Sorry. Wrong a second time." << endl; break; case 3: cout << "Ah, this is harder than it looks, isn't it?" << endl; break; default: cout << "It must be getting pretty frustrating by now!" << endl; break; }
若某个case标签与表达式值温和,便开始执行case标签之后的语句,若没有break会一直执行到switch语句的最下面,”向下穿越“模式
switch(next_char){ case 'a': case 'A': case 'e': case 'E': case 'i': case 'I': case 'o': case 'O': case 'u': case 'U': ++vowel_cnt; // 若为元音字母,计数值加一 break; // ... }
-
其他:if-else语句,while循环,continue语句,break语句等
1.5 如何运用Array和Vector
使用可存放连续整数值的容器类型
-
C++内置的array类
定义array:指定其元素类型,命名,并指定所能存储的元素个数,其大小必须是个常量表达式
const int num_months = 12; int month[num_months];
-
vector boject
包含vector头文件,vector为模板类,必须用<>指定其元素类型,并指定其大小,其大小不一定是个常量表达式
#include <vector> int num_months = 12; vector<int> month(num_months); // 定义一个vector object,存储12个int,初始值为0
-
vector和array的元素索引都是从0开始的
month[0] = 1; month[1] = 2;
-
array的初始化:使用初始化列表
int elem_seq[seq_size] = { 1, 2, 3, // Fibonacci 3, 4, 7, // Lucas 2, 5, 12, // Pell 3, 6, 10, // Triangular 4, 9, 16, // Square 5, 12, 22 // Pentagonal };
初始化列表的元素个数不能超出array的大小,若小于array的大小,则剩余元素被初始化为0
-
vector的赋值
(1) 逐个元素按索引赋值
(2) 使用已初始化的array初始化vector
int elem_vals[seq_size] = { 1, 2, 3, // Fibonacci 3, 4, 7, // Lucas 2, 5, 12, // Pell 3, 6, 10, // Triangular 4, 9, 16, // Square 5, 12, 22 // Pentagonal }; vector<int> elem_seq(elem_vals, elem_vals + seq_size); // elem_vals和elem_vals + seq_size这两个值都是实际内存位置,标示了用以将vector初始化的元素范围
-
elem_seq.size()
返回vector对象的大小
1.6 指针带来弹性
-
一个未指向任何对象的指针,其地址值为0,称为null指针
int *pi = 0; // 定义int指针pi,初始化为null指针
-
对null指针进行提领操作,会导致未知的执行结果,可在提领操作前先检验该指针是否null指针
if(pi && *pi != 1024) *pi = 1024;
-
rand()与srand()是C语言标准库提供的伪随机数生成器,srand()的参数是随机数生成器种子,每次调用rand()将返回介于0和"int所能表示的最大整数"之间的一个整数
# include <cstdlib> srand(seq_cnt); seq_index = rand() % seq_cnt; // seq_index为[0, seq_cnt)之间的一个整数
-
class object指针
#include <iostream> #include <vector> #include <cstdlib> using namespace std; int main(){ int vals[] = {1, 1, 2}; vector<int> fibonacci(vals, vals + 3); vector<int> lucas(vals, vals + 3); vector<int> pell(vals, vals + 3); vector<int> triangular(vals, vals + 3); vector<int> square(vals, vals + 3); vector<int> pentagonal(vals, vals + 3); const int seq_int = 6; vector<int>* seq_addrs[seq_int] = { // seq_addrs是个array,存放元素类型为vector<int> * &fibonacci, &lucas, &pell, &triangular, &square, &pentagonal }; srand(seq_int); int seq_index = rand() % seq_int; vector<int> *current_vec = seq_addrs[seq_index]; // if(!fibonacci.empty() && fibonacci[1] == 1) // cout << fibonacci[1]; if(current_vec && !current_vec->empty() && (*current_vec)[1] == 1) cout << (*current_vec)[1]; return 0; }
(1) 通过名字fibonacci访问vector对象的成员时,通过dot成员选择运算符
.
进行;通过指针时,必须改用arrow成员选择运算符->
(2) 使用下标运算符时,必须先提领指针,即
(*current_vec)[1]
1.7 文件的读写
-
对文件进行操作需要包含fstream头文件
#include <fstream>
-
ofstream对象为供输出用的file stream对象
ofstream outfile("seq_data.txt"); // 若文件不存在则创建,若存在则打开并用于输出,且原数据会被丢弃 ofstream outfile("seq_data.txt", ios_base::app); // 以追加模式(append mode)打开文件,新数据会被追加到文件末尾 if(!outfile){ // 检查文件是否成功打开,若不成功则输出错误信息 cerr << "Oops! Unable to save session data!\n"; }else{ // 打开成功,将数据写入outfile outfile << user_name << ' ' << num_tries << ' ' << num_right << endl; }
endl
是iostream library定义的操作符,它会插入一个换行符,并清除输出缓冲区的内容 -
ifstream对象为供输入用的file stream对象
ifstream infile("seq_data.txt"); // 以读取模式打开infile, 若不能成功打开则ifstream为false,若成功则读取位置设置为文件起始处 int num_tries = 0, num_right = 0; if(!infile){ // 文件打开不成功,输出错误信息 cerr << "Cannot open the file!\n"; }else{ string name; int nt, nr; while(infile >> name){ // 一旦读到文件尾,则退出循环 infile >> nt >> nr; if(name == user_name){ cout << "Welcome back, " << user_name << endl; cout << "Your current score is " << nr << " out of " << nt << endl; cout << "Good Luck!" << endl; num_tries = nt; num_right = nr; } } }
-
fstream对象进行同时读写一个文件
fstream iofile("seq_data.txt", ios_base::in|ios_base::app); if(!iofile){ cerr << "Cannot open the file!\n"; }else{ iofile.seekg(0); // 将文件重新定位至起始处 string name; int nt, nr; while(iofile >> name){ // 一旦读到文件尾,则退出循环 iofile >> nt >> nr; if(name == user_name){ cout << "Welcome back, " << user_name << endl; cout << "Your current score is " << nr << " out of " << nt << endl; cout << "Good Luck!" << endl; num_tries = nt; num_right = nr; break; } } num_tries++; num_right++; iofile << user_name << ' ' << num_tries << ' ' << num_right << endl; }
以追加模式打开文件时,会定位至文件末尾,应使用
iofile.seekg(0);
将文件重新定位至起始处