c++ primer--第五版--第一部分--c++基础
第二章:变量和基本类型
1. c++11新增long long 类型, 新增空指针nullptr类型。
2. 可寻址的最小内存块--字节, 存储的基本单元--字(通常由几个字节组成)
3. 有符号和无符号做运算时,有符号会自动转换成无符号数。
4. 变量的声明和定义,声明并不占内存空间(表明类型和名字),定义的时候才分配具体的内存空间,可以多出声明,但只能一处定义。 注意区分初始化和赋值,初始化是变量定义的时候赋初值,赋值是将原来的值替换为一个新值。
5. 列表初始化 & 初始化列表
列表初始化是一种初始化方式,通过大括号{xxx}来初始
初始化列表指的是一个用来初始化的列表{xxx}。
默认初始化:函数内的变量不会默认初始化,全局变量会初始化为0,类中的变量初始化取决于类自己。
6. 显示访问一个全局作用域的变量-- 域运算符:: 表示全局作用域, 当外层和函数内都定义了b变量是,要使用外层的变量b则需要提供访问方式::b
7. 引用 & 指针:
引用:
引用必须被初始化 --不能重新赋值,定义时赋值,和const类似
引用不能重新绑定新对象
引用本身是一个别名,不是一个对象(引用实质上是通过指针实现,引用的大小和指针相同,直接用sizeof求引用是求的引用对象的大小,将引用变量放到一个类中,求这个类的大小可以求出引用本身的大小)
指针:
指针本身是一个对象
指针不用赋初值
void *可以保存任何类型的指针,函数参数为void *时可以传入任何类型的指针。
8. const修饰符
const对象必须初始化
默认情况下,const对象仅在当前文件内有效,extern声明后可以跨文件(static即使extern也不能跨文件)
const和引用:(常量引用)
常量引用不仅可以引用一个非常量的左值,还可以引用一个右值如 const int &a = 32;
还可以引用一个非匹配类型但是能转换成对应类型的值如 double b = 3.3; const int &a = b; 因为实际实现是将b转成了一个临时int对象,在用这个临时对象给const引用赋值(利用了const引用可以指向临时变量的特性,也就是常量引用可以引用右值的特性)
const和指针:
const修饰指针可以分为顶层const(修饰指针变量/离变量近)和底层const(修饰指针指向的内容/离变量远) const int *const p = NULL; 前一个为底层, 后一个为顶层
底层const指针:表示指针指向的是一个常量对象
允许将一个指向常量的指针指向非常量,这样不能通过这个常量指针修改这个非常量的值。
底层const修饰的是指向的对象,因此属性和 指针、引用等符合类型的基本类型部分有关。
当进行拷贝操作时,顶层const无影响,但是底层const不可忽略
int i = 32;
const int *p1 = &i; // 底层const
int *const p2 = &i; // 顶层const
int *p3 = p2; // 顶层const无影响,可以赋值 (可以通过p3修改指向的内容)
int *p4 = p1; // 错误,底层const影响(其实可以换个角度理解,p1底层const修饰,指向的是一个const对象,而p4是一个非底层const的指针,如果p4指向p1指向的对象,则可以通过p4改变对象的值,这是不合理的)
顶层const指针:表示指针本身是一个常量指针
常量指针必须初始化,一旦初始化后就不能改变指针的指向。
const int a = 3; a的const是一个顶层const,因为const修饰的是左边的a变量。
在赋值时,非const对象可以给const对象赋值,const对象不能给非const对象赋值,在传递时,const对象可以通过强制类型转换const_cast去掉const属性传递给非const对象。
9. constexpr(另外单独写这个修饰符的作用)
常量表达式:在编译过程就能得到计算结果的表达式(编译阶段就能确定其常量值)
const int a = 39;
const int b = a + 1; // 是常量表达式,编译阶段可获取常量值
const int sz = get_size(); // 不是常量表��式, sz本身是一个常量,但是必须在运行时才能获取常量值
constexpr:c++11新规,将变量声明为constexpr类型以便由编译器来验证边浪的值是否是一个常量表达式。constexpr变量一定是一个常量,并且必须用常量表达式初始化(将常量值确认时机提前到编译阶段)
constexpr int sz = get_size(); // 只有当get_size()是一个cosntexpr函数时才是一条正确的声明语句。
constexpr修饰函数:所有形参和返回值类型都是constexpr
constexpr和指针:constexpr修饰指针时,表示的是顶层指针,一个常量指针。(constexpr常量属性,修饰对象本身)
10. 别名
typedef: typedef double xxx;
using: using xxxx = double;
const修饰别名:修饰的是对象本身,如 typedef double *dp; dp为都double*指针, const dp point; const修饰的是常量指针,修饰的是指针本身,顶层指针。
11. auto: auto value = value1 + value2 根据加完的结果来推到出类型。auto变量必须赋初值。
auto会忽略顶层const,底层const会保留下来
const int a = 32; auto b = a; b会忽略a的顶层const
const int *c = &a; auto p = c; p会继承c的底层属性。
auto e = &a; 对常量取地址是一种底层const(指针指向的是一个常量对象:const int *p = &a)
auto &f = 32; 错误不能,不能引用一个字面量。
const auto &g = 32; 可以引用一个字面量
12. decltype:c++11引入,推倒表达式的类型,相对于auto,decltype只是获取类型,而不获取初始值。
int a = 3;
int func() { return 3;}
decltype(a) b = a;
decltype(func()) c = b; // 编译器并不实际调用函数func,而是使用当func调用时的返回类型。decltype(func)返回函数类型,decltype(func)* 表示函数指针。
decltype返回的类型包括所有限定,比auto更灵活
const int *p = &a; decltype(p)返回的是const *p类型
const int &b = a; decltype(b)返回的是const int &类型。
如果decltype返回的类型是引用类型,需要赋初值。
decltype(())返回的是引用类型:
decltype((a)) 返回的是 int &类型。
13. struct / class [className] {xxx; xxx; xx;} instanceName1, *pInstanceName2;
在类或者struct声明后面直接跟名字的情况,这种是实例对象,instanceName1是一个实例化的对象,pInstanceName2是一个实例化对象指针。
14. c++11新规定,可以为类中的数据成员提供一个类内初始值。
15. 头文件通常包含哪些只被定义一次的实体,如类,const和constexpr变量。
头文件一旦改变,包含了该头文件的源文件都需要重新编译
头文件保护符:
#ifndef _XXXX_XXXX_H
#define _XXXX_XXXX_H
#endif
第三章:字符串、向量和数组
1. using 声明作用域:using namespace::name;
2. string
构造方法:string s1; string s2(s1); string s2 = s1; string s3("value"); string s3 = "value" string s4(n, 'c')
支持下标访问,重载了+、==、>=、=、>>运算符,自带empty(), size() 函数
3. 范围for
for (auto a : str) { cout << a << endl;}
4. vector 容器
是一个类模板
构造函数:
vector<T> v1;
vector<T> v2(v1); // 拷贝副本
vector<T> v2 = v1; // 内部元素值拷贝
vector<T> v3(n, val); // n个val 如果写成 vector<T> v3{n, val};指的是两个值n和val
vector<T> v4(n); //n个默认值
vector<T> v5{a,b,c...}; //列表初始化 vector<T> v5(a,b,c...)这种写法是错误的
vector<T> v6 = {a,b,c...}
重载了=、==、!=、<、<=等运算符 (需要内部元素提供对应操作符的重载)
提供了push_back(t),size(),empty()等函数。
能用下标方式访问已存在元素,但是创建空间不能用下标方式,vector的内存是动态增长的(这个后面看stl在深入),创建容器可以使用初始化或者push_back自动扩容。
5. 迭代器
方便的访问容器内的元素
容器提供的两个函数,begin()和end(); begin返回的是指向第一个元素的指针,end()返回的是指向最后一个元素的后面一个位置;
迭代器支持指针类型访问数据: -> 和 *
迭代器支持运算++、--、+=、+ 等,用来移动迭代器
迭代器有const版本 const_iterator,const版本迭代器不能通过迭代器改变元素。
6. 数组
不能使用数组来初始化另一个数组,不能直接使用数组对象,但是可以使用数组的指针和引用。
int arr[10] = { 0 }; int (*pArr)[10] = &arr; 数组指针 int (&pArr)[10] = arr; // ���组引用
不存在引用的数组(数组元素是引用的情况是不会有的:引用需要初始化,引用大小怎么确定)
下标方式访问数组的时候注意不要数组越界
decltype(arr)返回的是一个数组类型: decltype(arr) a = {a,b,c...};
7. 指针和数组
指针指向数组的时候指向的是第一个元素的地址。
c++11新增两个函数begin(arr)返回指向数组的第一个元素的指针,end(arr)返回指向最后一个元素的后一个位置的指针。这两个函数在iterator头文件中
数组下表可以为负数,当指针指向数组中间位置是,可以通过负数的下标来访问元素
arr + 1 和 &arr + 1 是不一样的。一个数元素步长移动,一个数组步长移动。
第四章:表达式
1. 一元运算符作用于一个运算对象,如&; 二元运算符作用于两个运算对象如 +-*/
2. 左值:在等号左边的值,表示对象的身份,有内存空间; 右值:在等号右边,表示对象的值(不能再内存中寻址),程序运行中的中间值大部分是右值。(若可对表达式用 & 符取址,则为左值,否则为右值)
c++11新增的右值引用&&,右值引用是用来支持转移语义的。转移语义可以将资源 ( 堆,系统对象等 ) 从一个对象转移到另一个对象,这样能够减少不必要的临时对象的创建、拷贝以及销毁,能够大幅度提高 C++ 应用程序的性能。消除了临时对象的维护 ( 创建和销毁 ) 对性能的影响。
3. 前置和后置++: 前置++是先++然后返回++后的对象,后置++是先++但是返回的是++前的对象。
对于 *beg = toUpper(*beg++) 这个表达式来说,行为是未定义的,关键在于先执行右边的++还是左边的*beg,如果先执行左边的,则beg最后值会+1, 如果先执行右边的,最后执行左边给beg赋值(赋的值是++前的对象),beg值不会发生变化;
4. 位运算符( & | ~ ^)和逻辑运算符(&& || !)
5. sizeof是个运算符而非函数,参数可以有括号,也可以没有括号; sizeof int ; sizeof(int); 逗号运算符求值顺序依次从左往右。
6. 类型转换:
隐式转换: int与double的转换、布尔与int的转化、 sign有符号想unsigned无符号转换、数组转换成指针、指针与int 0的转化、变量转化成常量、char *与string的转换、
显式转换: c风格的 cast-name<type>(experssion) , double b = 3.33; int a = (int)b; int c = int(b);
c++四种强制转换: static_cast , const_cast , dynamic_cast, reinterpret_cast;
去const属性用const_cast。只能改变运算符底层的const属性也就是指针的对象。
基本类型转换用static_cast。任何具有明确定义的类型转换,只要不包含底层const,都可以使用static_cast;基类指针强制转换为子类指针,相对于dynamic_cast更粗暴,不会做安全检查。非const转化为const
多态类之间的类型转换用dynamic_cast。将基类的指针安全的转向子类的指针类型。
不同类型的指针类型转换用reinterpreter_cast。指针转另一个类型指针,指针转非指针类型。
备注:参考https://blog.csdn.net/u010275850/article/details/49452373 后面专题笔记梳理。
顶层const和底层const: 顶层const离变量名比较近 int *const p,修饰变量的。底层const修饰指针对象的 int const *p ;
第五章:语句
5.1 语句
简单语句:用;代替,最好用括号代替
符合语句:多个语句组合成的语句块,用{ }包起来。
5.2 语句作用域:为了避免超出语句的作用范围,用{}把语句包起来,哪怕是空语句
5.3 条件语句:
if else:注意垂悬else的情况,避免方法是显示的表明作用域,通过{ }将if和else后的范围包起来。
switch case:注意每个分支后的break;case后必须是整形的常量表达式; default可以处理非正常case的异常情况。
5.4 迭代语句:
while:不明确循环次数的时候使用
for:传统for循环 for( ; ; ) { }
for:范围for循环, c++11引入, for(declaration : expression) { } , 可以结合auto使用
do while: 和while相比,do中的代码块至少执行一次。
5.5 跳转语句
break, continue, goto(goto label; label:xxxx;),return
5.6 try catch 异常处理
可以throw一个异常对象,异常对象可以在标准库中选如throw runtime_error("xxxx"), 也可以继承异常类。
没有finally处理分支。
常见的异常类:exception,runtime_error,range_error, overflow_error,underflow_error,out_of_range
第六章:函数
6.1 函数基础
函数调用,控制权转换,形参与实参,参数的列表,返回的类型,局部对象的生命周期,函数声明和定义,分离式编译
返回类型不能是数组和函数类型,但是可以返回数组的指针和函数的指针。
局部静态变量,只在第一次调用的时候初始化,直到程序结束才被销毁。
函数可以多处声明,但是只能定义一次。
6.2 参数传递
1 值传递:要修改外部数据,可以值传递指针来修改。如果是对象,会发生拷贝构造。
2 引用传递:直接引用外部变量,当这个变量相当大的时候,可以替换为这个变量的地址,值传递地址。
3 const形参:传入的值可以是const,也可以是非const。const形参和非const形参不能同时出现,不是重载,在编译的时候编译出来的名字一样。如果函数只是传递一个参数而不改变参数的值,尽量使用常引用传值。(对于指针来说,底层const和无底层const修饰的形参是重载,引用const和非const是重载)
const修饰引用或者底层const指针的时候,是重载
const修饰非引用,非指针时,不是重载。修饰顶层const也不是
4 数组形参:数组形参会被当做一个变量指针。形参可以定义成数组的引用,注意不是引用数组;
引用数组指的是数组里的每个元素都是引用;引用数组是非法的。
引用是个别名,没有内存空间,编译器也不知道给引用数组分配多少内存,引用元素定义出来就要赋初值,在数组中意味着定义一个引用数组马���就要给所有的元素赋值。
参数不能是数组,可以是数组的指针或者引用。
5 可变参数:(后面专题梳理,包括decltype的用法)
c++继承c的可变参数:省略符,只能出现在参数的最后一个位置。
c++11新增的两种:如果参数类型相同,使用initializer_list; 如果参数类型不同,使用可变参数模板
initializer_list: 表示特定类型值得数组
声明: void func(initializer_list<stirng> ls) 调用:func( { "xxx", "zzz", "eee"} )
6.3 返回类型和return语句
1. 无返回值函数 void
2. 有返回值的函数:
注意不要返回局部对象的引用和指针
可以返回数组指针和函数的指针
可以返回左值(vector和string的元素)
可以返回列表初始化,外面用容器接受,就像调用可变参数一样,里面用initializer_list接收,调用的时候调用列表{ }。
3 返回数组指针或者数组引用:
直接写起来可能比较复杂,可以使用typedef int arr[10]或者 using arr = int [10] 来表示数组的别名;
arr * func() { } 返回一个数组 等价于 int (*func( ))[10] { }
也可以使用c++11新增的尾置返回类型: auto func() -> int (*)[10] { } 使用auto代替返回值,具体的使用->后面代替。
如果有类型相似的对象,可以使用decltype来替换: decltype(arr ) * func() { } (decltype出来的是类型,需要加上*)
6.4 函数重载
形参的数量和类型不一致,函数名一致的函数为重载函数
返回值类型不在考虑范围内,顶层const形参也不在考虑范围内 (顶层const表示的是离变量近的const),const非指针非引用也不是
重载与作用域:如果函数内部重载了函数外部的同名函数,函数外部的同名函数在函数内就不可见了,即使是不同参数和类型的同名函数。这个时候在函数内部调用同名的外部重载函数会调用失败。强行匹配函数内部的同名函数
6.5 特殊用途的语言特性
1. 默认参数:默认参数必须出现在所有参数的末尾位置。给定的默认实参在函数声明的时候只能出现一次,多出函数声明的地方不能每个地方都对同一个函数参数赋默认值。 int func (int a , int b = 1);下一个声明的地方只能是 int func( int a = 1, int b),不能再次对b做默认参数声明。默认参数在构造函数上时需要注意用explicit来修饰构造函数,因为默认参数可能导致单参数发生隐式转换。
2. 内联函数:编译过程内联展开。内联不一定能成功,编译器可以忽略这个请求。内联函数一般放在头文件内。头文件和源文件任意声明了inline都行。
3. constexpr函数:指的是能用于常量表达式的函数。编译阶段就确认常量属性。const修饰可能会拖到运行阶段。
函数的返回类型及所有形参都得是字面值类型。函数体中必须只用一条return语句。返回的不一定是常量表达式
constexpr函数被隐式的定义成内联函数。constexpr函数放在头文件内。
4. 调试帮助:
assert预处理宏 、NDEBUG与处理变量。可以在编译的时候加入这个编译选项,也可以在文件中定义#define NDEBUG;
__FILE__: 文件名的字符串字面值
__LINE__: 当前行号
__TIME__:文件编译时间
__DATE__:文件编译日期
6.6 函数匹配
候选函数:函数名、可调用
可行函数:参数类型和个数匹配,或者类型能够转换后匹配
最优函数:参数严格匹配比例最高。
6.7 函数指针
函数指针可以直接用函数名赋值,也可以取函数名的地址来赋值。调用方式也可以直接fun() 也可以解引用 (*func)()
函数声明的时候如果某个参数为函数指针,直接将声明形式定义在参数位置即可,会转换成函数指针,类似于数组。也可以取别名后的函数指针。取别名可以使用typedef,using,decltype和尾置类型的方法。
第七章:类
7.1 定义抽象数据类型
1. this:成员函数通过一个名为this的额外隐式参数来访问调用它的那个对象。在成员函数后面加上const,const修饰的是this指针;
this指针本身是个常量指针,底层const修饰后,成了指向常量的常量指针
成员函数中的隐式this参数:T* const this; 当成员函数修饰尾置const后,成员函数不能改变类的成员,其实就是修饰的this指向的对象,所以隐藏的this指针形式为 const T* const this;
const修饰后的this指向的对象也是常量,const修饰后的函数不能修改对象中的数据。
成员函数后面加const后,这个函数的隐式对象为常量对象,此时在const函数内部就不能调用非const的函数了。(常量成员函数)
const成员函数不能调用非const成员函数,如果确实要调用,使用 const_cast强转*this对象为非const对象。
在函数内返回*this表示返回调用这个函数的对象本身。const成员函数以引用的形式返回的*this是常引用。
2. 构造函数
构造函数不能声明为const的,当我们创建类的一个const对象时,直到构造函数完成初始化过程后,对象才能真正的获取到其“常量”属性,构造函数在const对象的构造过程中可以向其写值。
只有当类没有声明任何构造函数时,编译器才可能会自动地生成默认构造函数,生成默认构造函数的前提条件式类中的所有成员都能够默认的生成初始值或者提供初始值,如果类中含有自定义类型并且这个类型没有提供默认构造函数,那么这个类就不能生成默认的构造函数。
c++11的新标准,如果要显示的使用默认构造函数,在构造函数后面加上 =default ; default表示使用编译器默认的构造函数,在声明的构造函数后面加上=default表示内联,也可以在源文件中加=default;
构造函数的初始值列表: 类(): a(0),b(222),c("sss") { } ; a、b、c就是构造函数的初始值列表。
7.2 访问控制与封装
1. struct的默认访问属性是public, class的默认访问属性是private
2. 友元:类的友元函数或者友元类可以访问类的所有成员,友元函数或者友元类可以声明在类的任何位置,并且在类中声明友元只是起了一个权限指定的作用,表明可以友元可以访问类中的成员,并不是通常意义上的函数或者类声明。如果要调用友元,还是要在其他头文件或者某个地方声明函数。友元函数在外部实现的时候可以在内部定义一个类对象,也可以在参数中使用这个类类型参数。函数声明最好和友元类放在同一个头文件中。
3. 友元关系不具备传递性,子类不继承友元关系。每个类自己控制自己的友元类和友元函数。
4. 把某个类的成员函数声明成一个类的友元函数时,在类中声明友元的时候需要带上自己的类作用域: friend void classxxx::func();
5. 友元函数可以在类中声明友元的时候顺便定义出来,但是在随后的类成员函数使用时,必须显示的在类的外面再次声明。理由是在类的内部声明友元friend,只是表明了这个函数可以访问类中的所有成员,但是这个函数对这个类来说不可见。所以必须显示的在外面声明这个函数。
7.3 类的其他特性
1. 起别名: typedef std::string::size_type pos; 或者 using pos = std::string::size_type
2. 内联: default修饰的构造函数是隐式内联的,在类的头文件中声明并定义的函数是隐式内联的,在头文件用inline修饰的函数是显示内联的,在源文件中也可以用inline来修饰函数表示内联。
3. mutable:在类中,const修饰的函数不能改变类的成员,但是如果类成员通过mutable修饰后,也能在const常量成员函数中修改这个mutable成员。
4. const成员重载,这里注意区分const参数和const修饰的成员函数,const修饰的成员函数和非const是两个不同的函数,const修饰的成员函数优先匹配常量类对象。
const修饰的指针或者引用参数时,如果是顶层const,const和非const版本是同一个函数。如果是底层const,则为重载版本。
const修饰的是值类型的参数是,非const和const版本是同一个函数,不会发生重载
5. 类的前向声明:就是把类提前声明了,在后面再定义类,在定义类之前,可以使用类名指针或者引用,用在参数或者返回值处,但绝不能定义实体对象。(编译器无法构造对象,不知道分配多少内存)
链表中应用了前向类,类对象表示一个节点,下一个节点往往就是自己的类型指针,这个时候类中包含自己的类型指针。
7.4 类的作用域
1. 类的成员函数的返回值类型如果是类中定义的类型,则在源文件定义这个类成员函数的时候,返回的值类型前面必须显示的写出作用域。
class::item class::func () { } 显示的表明item的作用域。
2. 类的解析:首先编译成员的声明,直到类全部可见之后才编译函数体。
3. 取别名重定义:如果在前面已经使用了typedef对某个类型去了一个别名如nameA,后面就不能再对其他类型或者本类型取一个同样名字nameA的别名。
4. 全局作用域运算符::
在类中如果定义了一个变量和类外的一个变量同名,类中的这个变量会覆盖外部的变量,访问的时候会默认访问类中的变量,这个时候如果要访问外部的变量,之间用::name 访问外部的变量。
7.5 类的构造函数
1. 构造函数的初始值列表: 注意和参数列表名字区分。
如果类成员有const或者引用的时候,必须使用初始值列表来初始化。const和引用的属性是声明就初始化,初始化一次。
初始化列表和构造函数内值的区别:初始化列表是直接初始化数据成员,构造函数内赋值是先经历了初始化,在进行赋值,在效率上不如初始化列表。
初始化列表的初始化顺序和成员数据的定义顺序有关,和列表的初始化顺序无关。所以尽可能让成员数据和初始化列表顺序一致。
2. 委托构造函数:委托构造函数就是使用类的其他构造函数来执行自己构造函数的初始化过程。非const版本的构造函数使用const版本的构造函数来委托。
定义一个基本的构造函数,后面的构造函数的初始化可以调用之前的构造函数
class (int a, string s, double b) : a(a), s(s), b(b) {}
class (int a , string s) : class(a, s, 0.0) { }
class (int a, string s, double b, string d) : class (a, s , b) , d(d) { }
3. 隐式的类的类型转换 explicit : 构造函数只有一个参数,这个参数类型的变量值可以隐式的转换成一个类对象。(只对一个参数的构造函数有效)
string ss = "xxxx", 此时“xxxx”是一个char *类型的字符串,但是string类型的构造函数之一可以接受char*,这个时候“xxxx”就被隐式的转换成了string(‘’xxxx"),然后给ss赋值。
抑制构造函数的隐式转换,可以在单一参数的构造函数前面加上explicit,这样调用string ss = “xxxx”就会失败,只能显示的转换string ss = string("xxxx"),或者使用强制类型转换
多参数的构造函数如果提供了默认参数也会发生隐式的类类型转换。
string ss = static_cast<string>("xxxx");
4. 聚合类:所有成员都是public,没有任何构造函数,没有类内初始值,没有基类也没有virtual,类似一个struct,用来保存数据
5. 字面量常量类: