Essential C++读书笔记
一、C++编程基础
1、对象初始化(两种不同的初试化语法)
-
int num_tries = 0, num_right = 0
-
int num_tries(0)//构造函数语法,主要用来处理“多值初试化”
-
#include<complex> complex<double> purei(0,7);
2、特殊字符
在Windows操作系统下以常量字符串表示文件路径时,必须以“转义字符”表示反斜线字符
”F:\\essential\\pragrams\\charpter1\\ch1_main.cpp“
3、const 常量
被定义为const的对象在获得初值之后无法进行变动
const int max_tries = 3;
const double_PI = 3.14
4、条件判断
switch:
注意:当某个标签和switch的表达式值吻合时,该case标签之后的所有case标签也都会被执行,除非明确使用break来结束执行动作
switch(num_tries)
{
case 1:
cout<<"\n";
break:
case 2:
cout<<" ";
break;
default:
cout<<" ";
break;
}
5、arrays和Vectors
初始化:利用已初始化的array作为vector的初值
int elem_vals[seq_size]={1,2,3,4,5,6,7,8};
vector<int> elem_seq{elem_vals,elem_vals+seq_size};
vector可以知道自身的元素个数
elem_seq.size()
6、指针
防止对NULL指针进行操作可以检验该指针所含有的地址是否为0
if(pi && *pi != 1024)
{
*pi = 1024;
}
对于vector的指针的声明以及初始化
vector<int> *pv = 0;
定义vector的指针数组
vector<int> *seq_addrs[seq_cnt]={&fibonacci, &lucas, &pell, &triangular}
rand()和srand()函数
rand()每次回返回一个介于0和int所能表示的最大整数间的一个整数,srand()的参数是所谓的随机数产生器种子(seed),将随机数产生器的种子seed设为5就可以将rand的返回值限制在0-5之间
7、文件的读写
声明outfile后,如果指定的文件不存在,便会产生指定的文件,如果指定的文件存在,则该文件会开启用来输出,并且原文件中存在的数据会被丢弃
#include<fstream>
ofstream outfile("seq_data.txt");//输出模式
ifstream infile("seq_data.txt");//读取模式
fstream iofile("seq_data.txt",ios_base::in|ios_base::app)//读写模式,追加模式
以追加模式开启文件
ofstream outfile("seq_data.txt",ios_base::qpp);
if(!outfile)
cerr<<"Unable to save!"//cerr代表标准错误输出设备
else
outfile<<user_name<<endl;
当以附加模式开启文件时,文件位置会位于尾端,如果没有重新定位,就试着读取文件内容,那么立刻就会遇到“读到文件尾”的状况,seekg()可将文件位置重新定位至文件的起始处
iofile.seekg(0)
二、面向过程的编程风格
1、撰写函数
函数必须被申明才能调用,通常函数声明会被置于头文件,如引入cstdlib文件就是为了含入exit()函数声明
int fibon_elem(int pos)//即函数原型,不需要写函数主体
使用**exit()**必须包含头文件
#include<cstdlib>
2、调用函数
当我们调用一个函数时,会在内存中建立起一块特殊区域,称为**“程序栈”**,这个特殊区域提供了每个函数参数的储存空间,同时也提供了函数所定义的每个对象的内存空间,一旦函数完成,这些内存就会被释放掉
3、引用
int ival = 1024;//对象,类别为int
int *pi = &ival;//指针
int &rval = ival;//引用,代表一个int对象
int jval = 4096;
rval = jval;//即把jval赋值给rval所代表的对象,即ival
C++不允许改变引用所代表的对象
4、生存空间及生存范围
除static以外,函数内定义的对象只存活与函数执行之际
对象如果在函数以外声明,则从其声明点至文件尾端都是可视的,也该对象必定被初始化为0
5、动态内存管理
不管是local scope还是file scope,对我们而言都是由系统自动管理,还有第三种为dynamic extent(动态范围),其内存由程序的自由空间配置(也称堆内存)而来,此种内存必须由程序员自行管理,其配置由new表达式达成,释放由delete表达式完成
int *pi;
pi = new int(1024);
int *pia = new int[24];//配置数组,C++不可以从heap配置数组的同时为其元素设定初值
delete pi;//
delete []pia;//删除数组中的所有对象
如果不适用delete,则从heap配置来的对象永远不会释放,即内存泄漏
6、提供默认参数值
void display(const vector<int> &vec,ostream &os =cout)//将cout设置为默认的ostream参数
{
for(int ix = 0;ix<vec.size();++ix)
os<<vec[ix]<<' ';
os<<endl;
}
默认参数值提供的两个规则
- 如果为某个参数提供了默认值,那么该参数右侧的所有参数都必须也具有默认值
- 默认值只能制定一次
7、声明inline函数(还不懂)
将函数声明为inline,表示要求编译器在每个函数调用点上将函数的内容展开,面对一个inline函数,编译器可将该函数的调用改以一份函数码副本取代
inline bool_fibon_elem(int pos, int &elem)
{
//内联函数必须跟函数定义一起,不然无法构成内联函数
}
定义在类声明之中的成员函数将自动地成为内联函数
class A
{
public:
void FOO(int x, int y)
{
///自动地成为内联函数
}
}
调用函数过程**:调用前要先保存寄存器,并在返回时恢复,复制实参,程序还必须转向一个新位置执行(因此会在消耗一定的时间)
引入内联函数的目的是为了解决程序中函数调用的效率问题,这么说吧,程序在编译器编译的时候,编译器将程序中出现的内联函数的调用表达式用内联函数的函数体进行替换,而对于其他的函数,都是在运行时候才被替代。这其实就是个空间代价换时间的i节省。所以内联函数一般都是1-5行的小函数。在使用内联函数时要留神:
- 在内联函数内不允许使用循环语句和开关语句;
- 内联函数的定义必须出现在内联函数第一次调用之前;
- 类结构中所在的类说明内部定义的函数是内联函数。
8、函数重载(略简单,应在后期有补充)
参数表不相同的多个函数可以拥有相同的函数名称
void display_message(char ch);
void display_message(const string&);
void display_message(const string&, int);
void display_message(const string&, int, int);
9、模板函数(后期应有补充)
函数模板可以将单一函数的内容与希望显示的各种vector型别捆绑起来
function template以关键词template开场,其后紧接着以<>包围起来的一个或多个识别名称,这些名称表示我们希望延缓决定的数据类别,每当用户使用这个模板产生函数时,他就必须提供确实的类别信息
template <typename elemType>
void display_message(const string &msg, const vector<elemType> &vec)
{
cout<<msg;
for(int ix = 0; ix<vec.size();++ix)
{
elemType t = vec[ix];
cout<<t<<' ';
}
}
vector<int> ivec;
string msg;
//....
display_message(msg, ivec);//将elemType绑定为int
//....
vector<string> ivec;
string msg;
//....
display_message(msg, ivec);//将elemType绑定为string
10、函数指针
函数指针:必须指明起所指向函数的返回值类型及参数表
const vector<int> * (*seq_ptr)(int);//seq_ptr是一个指针,指向具有const vector<int>* (int)形式的函数
bool seq_elem(int pos, int&elem, const vector<int> *(*seq_ptr)(int))
{
//调用seq_ptr所指的函数
const vector<int> *pseq = seq_ptr(pos);//函数名即是指针
if(!pseq)
{
elem = 0;
return false;
}
elem = (*pseq)[pos-1];
return true;
}
11、头文件
-
头文件扩展名习惯上是**.h**,标准程序库例外,他们没有扩展名。
-
函数的定义只能有一份,不过可以有许多份声明。我们不把函数的定义纳入头文件,因为同一个程序的多个代码文件可能都会含入这个头文件
-
如果文件被认定为标准的、或项目专属的头文件,便用尖括号将文件名括住;编译器搜寻此档时,会现在某些默认的驱动器目录中找寻。如果文件名由成对的双括号括住,此文件便被认为是一个用户自行提供的头文件;搜寻此文件时,会由含入此文件之文件所在的驱动器目录开始找起。
一次定义规则的例外:
- inline函数的定义,为了能够扩展inline函数的内容,在每个调用点上,编译器都能取得其定义,这意味着我们必须将inline函数的定义置于头文件,而不是把它放在各个不同的程序代码文件
- const object的定义只要一出文件之外便不可见,这意味着我们可以在多个程序代码文件中加以定义,不会导致任何错误
//NumSeq.h
bool seq_elem(int pos,int &elem);
const vector<int> *fibon_seq(int size);
const vector<int> *lucas_seq(int size);
const vector<int> *pell_seq(int size);
//....
const int seq_cnt = 6;//之所以可以定义是因为是const object
extern const vector<int>* (seq_array)([seq_cnt])(int);
三、泛型编程风格
1、Iterators(泛型指针)
每个标准容器都提供一个名为**begin()
**的函数,返回一个iterator
指向第一个元素,end()
返回的iterator
指向最有一个元素
vector<string> svec;
vector<string>::iterator iter=svec.begin();
//::表示此iterator乃是位于string vector 定义式内的嵌套型别
设计一个find()
函数,让它同时支持两种形式:一对指针,或是一对指向某种容器的iterators
template<typename IteratorType, typename elemType>
IteratorType find(IteratorType first,IteratorType last,const elemType &value)
{
for(;first!=last;++first)
if(value==*first)
return first;
return last;
}
2.所有容器的共通操作(包括string类)
==
和!=
,返回true或者false=
,将某个容器复制给另一个容器empty()
,容器无任何元素时返回true,否则返回falsesize()
,返回容器内的元素数目clear()
删除所有元素begin()
end()
insert()
将单一或某个范围内的元素安插到容器内erase()
将单一或某个范围内的元素删除,返回的iterator
指向被删除元素的下一个元素
insert()
和erase()
视容器本身为循序式或关联式而有所不同
3.序列式容器
- vector:动态数组,需要一块连续的内存
- list:双向链表
- deque:队列,queue便是以deque实现的
产生容器的方法
//1.产生空的容器
list<string> slist;
v