$ a.out <infile >outfile
P31
对于引用的操作,实际上是作用在引用所引的对象上。,使用引用作为参数有两个用处:1)避免拷贝 2)返回额外的值
1、P45
2、指向指针的引用 P52
引用本身不是一个对象。
int i = 42;
int *p;
int *&r = p;
r = &i;
*r = 0; //最终i被设置成了0
3、对常量的引用 P54
const int ci = 1024;
const int &r = ci;
int i = 42;
const int &r1 = i; //允许const int&绑定到一个普通int对象上
4、const和指针 P56
const int i = 42;
int *p = &i; //错误
const int *cp = &i;
int a = 43;
cp = &a; //正确,但是不能通过*cp = 0;这种操作来改变a的值。因为const int *cp 所指向的内容不能变。
5、指针类型别名
typedef char *pstring;
const pstring p = 0; //指向char的【常量指针】
const char * p = 0; //指向const char的【指针】,即指向常量字符的指针
6、数组引用
int &a[10] = ? //不存在引用的数组,因为数组的元素应该为对象,引用不是对象。
int (&ar)[10] = arr; //正确,P102 表示ar是一个引用,它【引用的对象】是一个大小为10的数组,数组中元素的类型是int
int arr[10];
int (*p)[10] = &arr; //【指向数组】的【指针】
7、设置形参为引用 P189
bool isShorter(const string &s1, const string &s2) //把形参定义为【对常量】的引用
{
return s1.size() < s2.size();
}
8、尽量使用常量引用
P192
find_char(string &s, char c,...) //s只能接受普通引用
find_char("hello world", '0', ...); //这样的调用是错误的
9、不要返回局部对象的引用或指针 P201
要想确保返回值安全,我们应该确定:引用所引的是在函数之前已经存在的哪个对象?
10、引用返回左值
P202
char & get_value(string &s, int ix);
get_value(s,0) = 'A'; //将s[0]的值改为A
//如果返回的是常量引用,我们不能给其赋值。
vector<int> v = {0,1,2,3,4};
//&r代表会改变v中的数据,用引用
for(auto &r : v)
{
r *= 2;
}
1、size_type
string::size_type
vector::size_type //错误
vector<int>::size_type
2、difference_type
带符号的整数型
3、数组下标
size_t (cstddef头文件)
迭代器类型定义:
vector<int>::iterator it;
string::const_iterator is;
【谨记】但凡是使用了迭代器的循环体,都不要向迭代器所属的容器添加元素。
1、
char a[] = "c++"; //维度为4,后面自动添加空字符
char a1[] = {'c','+','+'}; //维度为3,没有空字符,也不会自动添加
int * beg = begin(a);
int * end = end(a); //指向数组a的尾后元素 包含iterator头文件
2、使用数组初始化vector对象
int arr[] = {1,2,3,4,5,6};
vector<int> vec(begin(arr), end(arr));
vector<int> vec2(arr+1, arr+4); //拷贝了2,3,4给vec2
3、指针和多维数组
P115
4、数组别名
P205
typedef int arrT[10]; //arrT是一个类型别名,它表示的类型是:含有10个整数的数组。
using arrT = int[10];
arrT * func(int i); //返回一个指向包含10个整数的数组的指针。
5、返回数组指针的函数
P205
int (* p)[10]; //p是一个指针,指向一个包含10个整数的数组。 int (* func(int i))[10]; //返回【数组指针】的函数 auto func(int i)->int (*)[10]; //返回数组指针
if(val == true) //只有当val等于1时才为真,最好不用使用布尔字面值(true/false)进行比较
if(val)
P145
const_cast: 去掉const性质,只适用于底层const
const char *c_p; //底层const,即所指的内容为常量
char *p = const_cast<char *>(c_p); //通过p写值是未定义的。
const_cast<int *>(c_p); //错误
throw runtime_error("*****");
P208
1、顶层const( 指针本身为常量)无法实现重载,即本身为一个常量。
而底层const可以实现重载,因为指针、引用所指向、引用的值为一个常量,在调用的时候可以通过判断是否是常量来选择。
int lookup(phone);
int lookup(const phone); //无法重载
int lookup(phone *);
int lookup(phone * const); //无法重载
int lookup(const phone *); //可以重载
2、const_cast和重载
const_cast<const string&>(s); //将普通的string &转换成常量引用
const_cast<string &>(s); //将常量引用转换为普通引用
P212
局部变量不能作为默认实参。
void ff(int);
void ff(short);
ff('a'); //char提升为int,调用ff(int)
void ff(long);
void ff(float);
ff(3.14); //二义性,3.14是double类型,既能转换为long也可以转换为float
1、函数指针形参
//函数指针形参
void fun(const string &s, bool pf(int,int)); //pf为函数类型,自动转换为指向函数的指针
void fun(const string &s, bool (*pf)(int,int)); //pf为指针类型
2、返回指向函数的指针
P223
typedef int (*pf)(int *, int); //pf是函数指针,指针类型
pf f1(int); //①f1是一个函数,返回指向函数的指针
int (*f1(int))(int *, int); //②与上面的等价
//f1是一个函数,返回一个指针,该指针是一个【函数指针】,指向一个函数(返回值为int,参数为int *, int的函数)
//尾置返回类型
auto f1(int)->int (*)(int *, int); //③
1、this是一个指向调用函数的对象本身,是一个常量指针,该指针的值不可改变。
A a; //类A,定义对象a
a.fun(); //当a调用成员函数时,this指向了a本身。
2、返回*this的成员函数
P247
&Screen move()
{ ...; return *this;} //返回的是调用该接口的对象的引用,注意返回的是&Screen,而不是Screen。
&Screen set()
{ ...; return *this;}
//那么我们这样调用
myScreen.move().set(); //相当于myScreen.move() myScreen.set()
const &Screen display() const
{ ...; return *this;} //this是一个指向常量的指针,而*this将是一个常量对象。
myScreen.display().set(); //display返回【常量引用】,调用set时将会出错,无法改变常量引用的值。
P246
class Window_mgr
{
private:
std::vector<Screen> s{Screen(24,80,'')}; //容器vector里装的是Screen类,Screen(24,80,'')只是初始了vector中的第一个块。
}
P241
友元关系不能继承。
友元与继承 P545
class Base
{
public:
void pub_men();
friend class Pal; //声明Pal类为友元
protected:
int prot_mem;
private:
char priv_mem;
};
class Sneaky : public Base
{
//派生类能访问保护成员,不能访问私有成员
friend void clobber(Sneaky &); //能访问Sneaky::prot_men P543
friend void clobber(Base &); //不能访问Base::prot_mem
int j; //j is private
};
class Sneaky2 : private Base
{
//派生类能访问保护成员,不能访问私有成员
//Sneaky2对Base是私有继承,因此在默认情况下继承来的成员都是Sneaky的私有成员。
//使用using声明改变访问属性 P546
};
class Pal
{
public:
int f1(Base b) {return b.prot_mem}; //Pal是Base的友元,可以访问其私有成员
int f2(Sneaky s) {return s.j}; //Pal不是Sneaky的友元,不能访问其私有成员
int f3(Sneaky s) {return s.prot_mem}; //Pal是Base的友元,可以访问派生类中基类的私有成员
};
Sneaky s1; //继承Base是public
Sneaky2 s2;
s1.pub_mem; //ok
s2.pub_mem; //pub_mem在派生类中是私有的了
1、构造函数初始值列表(必须用此方法的情况) P258
如果成员是const或者引用时,或者属于某种类类型且该类没有定义默认构造函数时,必须通过构造函数初始值列表为这些成员提供初始值。
class test{
public:
test(int a);
private:
int i;
const int ci;
int &ri;
};
//错误的构造函数
test::test(int a)
{
i = a;
ci = a; //错误,不能给常量赋值
ri = a; //错误,ri还未初始化呢,都不知道指向哪个鬼东西~
}
//正确的做法为使用:构造函数初始值列表
test::test(int a):i(a),ci(a),ri(a)
{
}
2、默认构造函数的作用 P262 ????
3、显示构造类的对象
item.combine(Sales_data(null_book)); //Sales_data(null_book)构造了一个Sales_data类的对象
P269
静态成员存在于任何对象之外。
static关键字只出现在类内部的声明语句中。P270
静态成员不能在类内部定义和初始化,必须在外部(类似于全局变量)。
double Account::rate = ...;
静态成员可以是不完全类型,可以是它所属的类的类型,而非静态成员只能是所属类类型的指针或引用。
静态成员可以作为默认实参,非静态成员因为其值本身属于对象的一部分,不能作为默认实参。
P329
每个适配器都定义两个构造函数,A a or A a(c)
P338
string sum = accumulate(v.cbegin(), v.cend(), string("")); //string上定义了+号运算,即链接字符串
string sum = accumulate(v.cbegin(), v.cend(), ""); //错误,const char *没有定义+号运算
str.substr(pos,n) //P322 拷贝POS位置开始的n个字符,并返回该子字符串string
P341
back_inserter接受一个指向容器的引用,返回一个与该容器绑定的插入迭代器。当我们通过此插入迭代器赋值时,赋值运算符会调用push _back将一个具有给定值元素添加到容器中。
P329
所有适配器都需要有添加和删除的能力,因此适配器不能构造在array之上,其是固定大小的。
stack、queue是默认基于deque实现的,priority_queue是基于vector实现的。
s.pop
s.push(item)
s.emplace(args)
s.top()
stack<int> intStack;
for(size_t ix=0; ix != 10; ++ix)
{
intStack.push(ix);
}
lambda表达式:(p346)
[capture list] (parameter list) -> return type { function body} // capture list 是从lambda所在函数中传递过来的局部变量(非static)列表
void biggies(vector<string> &words, vector<string>::size_type sz)
{
elimDups(words);
auto wc = find_if(words.begin(), words.end(), [sz](const string &a){ return a.size() >= sz; });
//++ 从words起始到终点,找到一个大于等于sz的words
}
lambda值捕获和引用捕获的区别(P350),应该尽量避免捕获指针或引用。而ostream对象不能被拷贝,此时只能使用引用捕获。
可变lambda
size_t v1 = 42;
auto f = [v1] () mutable { return ++v1; };
v1 = 0;
auto j = f(); // j为43
例如tuple类型的初始化
tuple<size_t, size_t, size_t> a = {1,2,3}; // 错误
tuple<size_t, size_t, size_t> a{1,2,3}; // 正确,直接初始化
程序结束、缓冲区满、使用了操纵符如endl、设置unitbuf、输出流关联到了另外一个流(该流读写时)的时候都会导致输出缓冲区被刷新。cin和cerr都关联到了cout,那么读cin或写cerr时,都会导致cout的缓冲区被刷新,而cerr是立即刷新的。
cout<<unitbuf; //所有输出操作都会立即刷新缓冲区
//do somethings
cout<<nounitbuf; //恢复默认
当我们使用基类的引用(指针)调用一个虚函数时,将发生动态绑定,运行的时候根据传入的实参决定运行的版本,是调用基类的还是派生类的。
P530
因为派生类对象中含有基类对应的部分,所有我们能把派生类的对象当成基类对象来使用,
也可以将基类的指针或引用绑定到派生类对象的基类部分。
当给基类的构造函数传递一个派生类对象时,实际运行的构造函数是基类中定义的那个,只能出来基类自己的成员。
将一个派生类对象赋值给一个基类对象时,实际运行的赋值运算符也是基类中定义的那个。
当我们用一个派生类对象为一个基类对象初始化、赋值时,只有派生类对象中的基类部分会被拷贝、移动或者赋值,它的派生类部分将被忽略掉。
P536
派生类向基类的类型转换只对指针、引用有效。
基类向派生类不存在隐式类型转换。
任何构造函数之外的非静态函数,都可以是虚函数。
动态绑定
double print_total(ostream &os, const Quote &item, size_t n) //使用基类的引用
{
//根据传入item形参的对象类型调用Quote::net_price(基类) or Derive_quote::net_price(派生类)
double ret = item.net_price(n);
double ret = Quote::item.net_price(n); //作用运算符,强行回避虚函数机制
}
如果虚函数包括默认实参,那么默认实参值由本次调用的静态类型决定。P539,通过基类的引用指针调用函数,则使用基类的默认实参,而不会在实际运行中选择派生类的。
我们不能创建抽象基类的对象,可以定义其派生类的对象,前提是其派生类中对基类的虚函数进行了覆盖。
引用和指针的静态类型和动态类型不同这一事实是C++语言支持多态性的根本。
防止继承的发生:final P533
P544
派生访问说明符的目的:控制派生类用户(包括派生类的派生类)对于基类成员的访问权限。
class Base
{
public:
void pub_men();
protected:
int prot_mem;
private:
char priv_mem;
};
class Sneaky : public Base //派生访问说明符位public
{
//派生类能访问保护成员,不能访问私有成员
};
class Sneaky2 : private Base
{
//派生类能访问保护成员,不能访问私有成员
};
Sneaky s1; //继承Base是public
Sneaky2 s2;
s1.pub_mem; //ok
s2.pub_mem; //pub_mem在派生类中是私有的了
P545 类的设计和受保护的成员
基类应该将其接口声明为公有的,将其实现分成两组:一组供派生类访问,保护的;一组只能由基类及其友元访问,为私有的。
P549
class Base{
int memfun();
};
class Derived : Base{
int memfun(int);
};
Base b;
Derived d;
d.memfun(); //错误,参数列表为空的memfun被隐藏了
d.Base::memfun();
inline/constexpr必须在中间、函数参数是const的引用(使得函数可用于不能拷贝的类型和大对象) P581
template <typename T, class U> inline/constexpr T min(const T &v1, const U &v2);
//类外部的成员函数
template <typename T>
ret-type Blob<T>::fun(...);
//在一个类模板作用域内,我们可以直接使用模板名(Blob),而不必指定模板实参(Blob< T >)。
template <typename T> Blob<T> Blob<T>::fun(...)
{
Blob ret = *this; //直接使用模板名
}
在一个类模板作用域内,我们可以直接使用模板名(Blob),而不必指定模板实参(Blob< T >)。
拷贝(赋值)构造函数的第一个参数必须是自身类型的引用,且其他额外参数必须有默认值。 P440
Foo& operator=(const Foo&)
{
//初始化运算、赋值
return *this; //返回左侧运算符对象的引用。
}
IO 类(istream ostream)不能被拷贝,因此只能通过引用来传递。P234
构造函数不能被声明为const。P235
如果成员是const或者引用时,或者属于某种类类型且该类没有定义默认构造函数时,必须初始化。P258
类成员的初始化顺序与其在类定义中的顺序一致。
如果一个构造函数为所有参数都提供了默认实参,则它实际上也定义了默认构造函数。P260
如果定义了其他构造函数,最好也提供一个默认构造函数。
非类型模板参数的模板实参必须是常量表达式。P581
拷贝(赋值)构造函数的第一个参数必须是自身类型的引用,且其他额外参数必须有默认值。 P440
虚函数在构造函数中,已经失去了虚函数的动态绑定特性。
不能用同时用static和const(放在函数参数列表后的修饰)修饰类的成员函数。P269
static公有成员,因为它们独立于class对象之外,我们不必产生对象也可以使用它们。
int *p(new int(42)); //p指向动态内存
int *p = new int[42];
typedef int arrT[42]; //数组类型别名,arrT表示42个int型的数组类型,是类型。
int *p = new arrT; //分配一个42个int的数组,p指向第一个int
int *p = new int[42](); //42个0的数组
delete [] p;
vector<int> ivec;
for(vector<int>::size_type i=0; i!=10; ++i)
{
ivec.push_back(i);
}
set<int> iset(ivec.cbegin(),ivec.cend());
set<int>::iterator it_set = iset.begin();
map<string,int> imap; //imap[key]表示值(value) key->value
auto it_map = imap.cbegin(); //it_map是指向一个pair<const string,int>对象的引用
it_map->first;
it_map->second;
ifstream &map_file; //P393
map_file >> key;
getline(map_file, value);
//文件每一行是这样的:word other stirngs
//那么key == word value将得到word之后的内容,包括word与other之间的空格
value = value.substr(1); //去掉word与other之间的空格
struct PersonInfo
{
string name;
vector<string> phone;
};
vector<PersonInfo> people;
PersonInfo info;
info.name ...
info.phone.push_back(...);
people.push_back(info);