一、Basic C++ Programming
1. 变量初始化的两种方式
· assignment运算符(=)初始化:沿袭自C语言,如果对象属于内置类型或者可以单一值初始化,这就没问题。例:string sequence_name = “Fibonacci”;
· constructor initialization syntax 构造函数初始化语法:在对象需要多个初值的情况,如complex类。例:complex<double> purei(0, 7);
2. array和vector
//用已初始化的array作为vector的初值
const int seq_size = 18;
int elem_vals[seq_size] = {
1, 2, 3, 3, 4, 7, 2, 5, 12
3, 6, 10, 4, 9, 16, 5, 12, 22};
vector<int> elem_seq(elem_vals, elem_vals + seq_size);
3. 指针的使用
一个没有指向任何对象的指针,地址为0(null指针),在使用指针前,必须在提领之前确定它的确指向某对象。为了防止对null指针进行提领,可以事先检验地址是否是0。if( pi && … )
4. rand()和srand()
#include <cstdlib>
const int seq_cnt = 6;
srand(seq_cnt);
seq_index = rand() % seq_cnt;
rand()和srand()都是标准库提供的伪随机数生成器,srand()的参数是所谓随机数生成器种子(seed),每次调用rand(),都会返回一个0和seq_cnt之间的一个整数,这里的值被限制在0~5。
5. 文件的读写
#include <fstream>
//以输出模式开启seq_data.txt
//如果指定文件不存在,会有一个文件被产生并打开供输出使用
//如果文件已存在,这个文件被打开用于输出,而原有的数据会被丢弃
ofstream outfile("seq_data.txt");
//不希望丢弃原有内容,可增加一个参数,以append model打开文件
//ofstream outfile("seq_data.txt", ios_base::app);
if(!outfile)
cerr << "Oops! Unable to save session data!\n";
else
//将数据写入
outfile << usr_name << ' ' << num_tires << ' ' << nun_right << endl;
//以读取模式(input mode)打开infile
istream infile("seq_data.txt");
if(!infile) {...}
else
{
string name;
int nt, nc;
//这条与语句的返回值就是从infile中读到的class object
//读到文件末尾返回false
while(infile >> name)
{
//文件的形式:anna 24 19
//分别把两个整数读到nt和nc中
infile >> nt > nc;
if(name == usr_name) {...}
}
}
//同时读写同一个文件,为了以追加模式打开,传入第二参数
fstream iofile("seq_data.txt", ios_base::in|ios_base::app);
if(!iofile) {...}
else
//开始读取之前,将文件重新定义至起始处
iofile.seekg(0);
//其他部分和上面的读取相同
当我们以追加模式来打开文档,文件位置会位于末尾。如果我们没有先重新定位,就试着读取文件内容,那么立刻就会遇上“文件结束”的状况。seekg()可将iofile重新定位至文件的起始处。由于此文件是以追加模式开启,因此任何写入操作都会将数据添加在文件末尾。
二、 Procedural Programming
1. pass by value && pass by reference
调用函数时,会在内存中建立一块特殊区域,就是程序堆栈(program stack),这个特殊区域提供了每个参数的存储空间,用pass by value的方式,使得对象在堆栈中复制了一份副本,对堆栈中的副本(形参)操作,一旦函数完成,这块堆栈内存就被释放掉,真正的自己(实参)没有发生任何改变。为了让形参和实参产生关联,要通过pass by reference的方式。
以pass by reference方式将对象作为函数参数传入时,对象本身并不会复制出另一份,复制的是对象的地址。函数对该对象的任何操作,都相当于是对传入的对象进行间接操作。用pass by reference的方式传参,还可以降低复制大型对象的额外负担。
但是注意下面情况,对根本不存在的对象进行寻址操作,是很不好的习惯。
vector<int> fibon_seq(int size)
{
vector<int> elems(size);
for(int ix = 0; ix < size; ++ix)
if(ix == 0 || ix == 1)
elems[ix] == 1;
else elems[ix] = elems[ix - 1] + elems[ix - 2];
return elems
}
这里以pointer或reference形式将elems返回,都不正确,因为elems在fibon_seq()执行完毕已不存在。如果将elems以传值方式返回,则不会产生问题,因为返回的是对象副本,它在函数之外仍然存在。如果返回的对象函数内的局部变量,就不能用传址的方式返回。除非我们用局部静态对象定义elems,如下:
vector<int>* fibon_seq(int size)
{
static vector<int> elems;
...
return &elems;
}
局部静态对象所处的内存空间,即使在不同的函数调用过程中,依然持续存在,elems的内容不再当fibon_seq()每次被调用时就破坏又被重新建立。现在可以将elems的地址安全返回。
2. 动态内存管理
int *pia = new int[24];
delete[] pia;
3. function overloading
函数重载机制,参数列表不相同(可能是参数类型不同,可能是参数个数不同)的两个或多个函数,可以拥有相同的函数名称。注意编译器无法根据函数返回类型来区分两个具有相同名称的函数。
4. function template
template<typename T>
void display_message(const string &msg, const vector<T> &vec)
{
cout << msg;
for(int ix = 0; ix < vec.size(); ++ix)
{
T t = vec[ix];
cout << t << ' ';
}
}
int main()
{
vector<int> ivec;//编译器将T绑定为int类型
string msg;
display_message(msg, ivec);
vector<string> svec;//编译器将T绑定为string类型
//...
display_message(msg, svec);
}
一般而言,如果函数具备多种实现方式,我们可将它重载,其每份实例提供的是相同的通用服务。如果我们希望程序代码的主体不变,仅仅改变其中用到的数据类型,可以通果function template达到目的。