Chap02 变量
声明和定义的区别
如果想声明一个变量而非定义它, 就在变量名前加上extern
extern int i; //声明i而非定义i
int j; //声明并定义j
变量只能被定义一次, 但可以被多次声明。
如果要在多个文件中使用同一个变量,就必须将声明和定义分离,变量的定义必须且只能出现在一个文件中, 其他用到该变量的文件必须对其进行声明, 却绝不能重复定义。
引用
引用即别名,引用本身不是一个变量,故不能定义引用的引用。引用只能绑定在对象上。
引用必须初始化,并且会和它初始绑定的变量永久绑定,不能对引用再赋值。
我笔记呢?
???
const 位于类成员函数末尾 指 常量成员函数,不能改变类内成员
void function()const; //常成员函数, 它不改变对象的成员变量.
Chap 03 字符串、向量和数组
string
初始化
//直接初始化
string s("hiya");
string s1(10, 'c');
//拷贝初始化(=)
string s2 = "hiya";
string的读写
string s;
cin >> s;
// string对象会自动忽略开头的空白从第一个字符读起,直到遇到下一个空白。
getline
string line;
getline(cin, line);
// getline参数是一个输入流和一个string, 从输入流读取内容直到遇到换行符
string的size()函数
返回值是string::size_type类型, 是一个无符号整数
string对象和字面值相加
string s3 = "hello" + ", " + s2 + '\n';
//必须确保 + 两侧至少有一个是string
string s6 = s1 + ", " + "world";
string s7 = "hello" + ", " + s2; //错误,不能把字面值直接相加
//切记 字面值 和 string 是不同的类型。
处理string中的字符
cctype头文件
isalnum(c) // 字母或数字时为真
isalpha(c)
ispunct(c) // 标点符号时为真
isdigit(c)
islower(c)
isupper(c)
tolower(c)
toupper(c)
访问string单个字符
[](下标运算符)返回的是该位置字符的引用
下标必须大于等于0且小于s.size() 超出范围会出现错误
同理,使用[]访问空字符串也会发生错误
Chap14 重载运算与类型转换
- 当一个重载运算符是成员函数时,this绑定到左侧运算对象。成员运算符函数的(显式)参数数量比运算对象少一个。
通常,不应该重载逗号、取地址、逻辑与(&&)和逻辑或(||)运算符
重载运算符应选择 : 成员函数 ?非成员函数 ?
有以下准则:
赋值 = 下标 [] 调用() 和成员访问 -> 运算符必须是成员。
复合赋值运算符一般是成员。
改变对象状态 或 与给定类型密切相关 如递增、递减、解引用通常是成员。
具有对称性的如算术、相等性通常应该是普通的非成员函数。
当我们把运算符定义成成员函数时,它左侧运算对象必须是运算符所属类的一个对象。
string u = "hi" + s; //如果+是string的成员,则产生错误。
输入输出运算符 >> <<必须是非成员函数。
如果要为类自定义IO操作,必须将其声明为非成员函数。由于IO运算符通常要读写类的非公有成员,所以IO运算符通常被声明为友元。
算术和关系运算符
通常把算术和关系运算符定义成非成员函数,因其一般不改变运算对象的状态所以形参都是常量的引用。
如果类同时定义了算术运算符和复合赋值运算符,通常应使用复合赋值来实现算术运算符。
相等运算符
类通过定义相等运算符检验两个对象是否相等。定义了 == 也应定义 !=,并且其中一个应把工作委托给另一个做。
关系运算符
如果要定义 < 运算符,当且仅当 < 的定义与 == 产生的结果一致时才定义 < 运算符
(如对于Sales_data, isbn, unit_sold, revenue都可以比较,且比较的定义不一定,可能是unit_sold大更好,也可能小更好,这种情况不适合定义 < 运算符)
赋值运算符
除了拷贝赋值 移动赋值 运算符
标准库vector还定义了第三种赋值运算符,接受花括号内的元素列表作为参数
vector<string> v;
v = {"a", "an", "the"};
为了与内置类型赋值运算符保持一致,该运算符返回左侧运算对象的引用
Str &str::operator=(initializer_list<string> il)
无论形参的类型是什么,赋值运算符必须定义为成员函数
复合赋值运算符
不一定非得是类的成员,但还是倾向于把所有赋值运算符定义在类内。为与内置类型复合赋值一致,复合赋值运算符也返回左侧运算对象的引用。
下标运算符
下标运算符必须是成员函数, 通常以访问元素的引用作为返回值。
如果一个类包含下标运算符,通常定义两个版本:一个返回普通引用;一个是类常量成员返回常量引用。
递增和递减运算符
定义递增递减运算符的类应同时定义前置、后置版本。这些运算符应被定义成类的成员。
定义前置递增/递减运算符
为与内置版本一致,前置运算符应返回递增或递减后对象的引用。
StrBlobPtr& operator++();
如何区分前置和后置运算符
前置和后置使用同一符号,运算对象数量和类型也相同。
为解决此问题,后置版本接受一个额外(不被使用的)int形参。当使用后置运算符时,编译器提供值为0的实参,其作用就是区分前置后置,并不参与运算。
StrBlobPtr operator++(int);//用不到形参,故无须命名
{
StrBlotPtr ret = *this;
++*this; //实际调用 前置++ 来完成 后置++
return ret;
}
后置运算符应返回对象(递增递减前)原值,返回一个值而非引用。
后置运算符调用前置版本完成实际的工作。
显式调用后置运算符时,必须为它的整形参数传一个值。
StrBlobPtr p(a1);
p.operator++(0);
成员访问运算符
解引用 * 箭头 ->
箭头运算符必须是类的成员,解引用运算符一般也是。
std::string& operator*() conts {
auto p = check(curr, "dereference past end");
return (*p)[curr];
}
解引用首先检查curr是否在作用域,是,则返回curr所指元素的一个引用
箭头调用解引用
std::string* operator->() const {
return & this->operator*();
}
这两个运算符都定义成const成员,因为获取一个元素不会改变对象的状态。
箭头运算符返回值的限定
箭头运算符不能丢失获取成员这个基本含义。
重载的箭头运算符必须返回 类的指针 或者 自定义了箭头运算符的某个类的对象
函数调用运算符
函数调用运算符必须是成员函数。一个类可定义多个不同版本函数调用运算符。
如果类重载了调用运算符,则可以像使用函数一样使用该类的对象。eg:
struct absInt {
int operator() (int val) const {
return val < 0 ? -val : val;
}
}
//调用方式
int i = -42;
absInt absObj;
int ui = absObj(i);
虽然absObj只是个对象而非函数,我们也能**“调用”它。
如果类定义了调用运算符,则该类对象称为函数对象(function object)**
函数对象常常作为泛型算法的实参
for_each(vs.begin(), vs.end(), PrintString(cerr, '\n'));
//第三个参数是PrintString的一个临时对象,用cerr '\n'初始化
lambda是函数对象
当我们编写一个lambda后,编译器将其翻译成一个未命名类的未命名对象。在lambda产生的类中有一个重载的函数调用运算符。
默认lambda不能改变它捕获的变量,故默认情况下产生的类中的函数调用运算符是const成员函数。
如果lambda声明为可变,则函数调用运算符不是const了。
stable_sort(word.begin(), word.end(),
[]const string &a, const string &b) {
return a.size() < b.size();
}
//行为类似下面类的一个未命名对象
class ShorterString {
public:
bool operator() (const string &s1, const string &s2) const
{ return s1.size() < s2.size(); }
}
//用类替代lambda后,可重写调用stable_sort
stable_sort(word.begin(), sort.end(), ShorterString());
表示lambda及相应捕获行为的类
lambda通过引用捕获变量时,程序确保引用所引对象存在,故编译器可直接使用引用无须在lambda产生的类中将其存储为数据成员。
而通过值捕获的变量则拷贝到lambda中,lambda产生的类必须为每个值捕获变量建立对应数据成员,同时创建构造函数,使用其捕获的变量来初始化。
auto wc = find_if(word.begin(), word.end(),
[sz](const string &a))
{ return a.size() >= sz };
//产生的类形如:
class SizeComp {
SizeComp(size_t n) : sz(n); //该形参对应捕获的变量
bool operator() (const string &s) const
{ return s.size() >= sz };
private:
size_t sz; //对应值捕获的变量
//合成类不含 默认构造函数、赋值运算符、默认析构函数
}
标准库定义的函数对象
标准库定义了一组类,分别定义一个执行命名的调用运算符,这些类被定义模板的形式,可为其指定具体应用类型,即调用运算符的形参类型。
//类型定义在functional头文件中
//算术 关系 逻辑
plus<Type> equal_to<Type> logical_and<Type>
minus<Type> not_equal_to<Type> logical_or<Typ>
multiplies<Type> greater<Type> logical_not<Type>
divides<Type> greater_equal<Type>
modulus<Type> less<Type>
negate<Type> less_equal<Type>
在算法中使用标准库函数对象
sort(svec.begin(), svec.end(), greater<string>());
对于指针同样适用(意义不明)
可调用对象与function
意义不明,先放着
重载、类型转换与运算符(意义不明)
转换构造函数和类型转换运算符 共同定义了 类类型转换, 也称为用户定义的类型转换
类型转换运算符
是类一种特殊成员函数,将一个类类型的值转为其他类型
operator type() const;
类型转换运算符面向任何可作为函数返回类型的类型,不允许转换成数组或函数类型,但可以转换成指针(包括数组指针和函数指针)或引用类型。
类型转换函数必须是类成员函数,不能声明返回类型,形参列表必须为空,通常应是const