前言
使用类,需要知道:类名,在哪里定义,支持的操作。定义的地方我没有想过。
一门语言的语法特征:内置类型、变量、表达式和语句、控制流、函数。
基本特征的补充:自定义数据、封装成库函数
章节
2- Variable and Basic Type
基本内置类型
- 数据类型:数据的意义和能执行的操作
sizeof(int) sizeof(long)
我电脑上都是4,longlong是8,这个一直有点懵- 类型转换:浮点数->
int
,保留整数部分,不近似 - 类型转换:无符号类型->超过范围的值,取模后的余数,例(-1赋给unsign char[0,255],结果256)
- 类型转换:无符号类型->超过范围的值,无定义
- 指定字面值的类型:加前后缀。prefix(u U L u8),suffix(u/U l/L ll/LL f/F l/L)
变量
- 变量提供一个可具名、可供程序 操作的存储空间。平时说明变量是什么总是模模糊糊的不够具体。用“可供程序操作”“存储空间”感觉很形象
- 对象:能存储数据并具有某种数据类型的内存空间
- 初始化 VS 赋值:创建时赋值;擦除当前值,用新值代替
- 声明 VS 定义:多次定义,一次声明;声明规定类型和名字(使用别处的定义的名字),定义创建实体;声明而不定义使用
extern
,不要初始化extern
标记的变量;给extern
初始化则抵消了extern
的作用 - 不太了解的关键字:
alignas alignof decltype reinterpret_cast typeid mutable explicit volatile thread_local
- sizeof 是关键字不是函数!!! 函数和关键字,如何区别?
- C++ 操作符替代名:
and bitand compl not_eq or_eq xor_eq and_eq bitor not or xor
复合类型
- 基于其他类型定义的类型,介绍引用和指针
- 引用一般指左值引用,
C++11
定义了右值引用 - 引用必须初始化的原因:定义时与初始值绑定在一起,初始化完成后也一直绑定在一起,无法重新绑定到另外一个对象。[编译器如何实现?]
- 指针 VS 引用:指针是指向另外一种类型的复合类型,存放某个对象的地址,使用时用取地址符(&),解引用符(*)得到指向的对象。
- 本身是变量,可复制 拷贝 先后指向多个对象
- 无须在定义时赋初值
- 指针初始化 (
NULL
) (C++11
字面值nullptr
) - 指针指向引用可以有;但是引用不是对象,所以不存在指向引用的指针
const 限定符
- 默认状态下,
const
对象仅在文件内有效:编译过程会替换成对应值;如果要在文件间共享,则声明/定义都要添加extern
- 引用类型必须与其所引用对象的类型一致,有两个例外
a. 初始化常量时可以用任意表达式做为初始值,只要改表达式结果能转化所引用的类型。
—-编译器会创建临时变量tmp进行类型转换,const是绑定到这个临时变量上的
b. 对const的引用可能引用一个并非const的对象 - 指向常量的指针不能改变所指对象的值,指针能不能修改其所指对象的值取决于所指对象的类型
- 只有指向常量的指针才能存放常量对象的地址
const double *const pip = &pi
从右到左阅读,离pip最近的是const,则pip是常量指针,double左边是const,则pip指向的是double常量对象- 顶层
const
(top-level)指指针本身是常量(int const pi),底层const
(low-level)指所指对象是常量(const int pi) - 拷贝操作,拷入拷出的对象必须具有相同的底层const资格,或者可以转换,一般非cosnt可以转换成const,反之不可
constexpr
修饰常量表达式则编译过程发现初始值不是常量表达式则报错constexpr
修饰指针,仅对指针有效,对其所指对象无效
处理类型
- 类型别名(type alias)
typedef double base , * p; // base是double别名,p是double * 别名
using SI = Sales_item; // SI是Sales_item的别名
- 声明后成为基本数据类型,包含复合类型(常量 指针)时,直接替换后理解容易出错
typedef char *pstring; // pstring 成基本数据类型
const pstring cstr = 0 ; // cstr是 指向char的常量指针
// 因为pstring是基本数据类型,所以const修饰是pstring(指针)
// 如果替换成const char* 则const char是基本数据类型,两种意义截然不同
- (C++11) auto类型说明符,编译器根据初始值判断类型。底层const不会被忽略,而顶层const会被忽略,如需保持则
const auto
- (C++11) decltype 类型指示符,返回数据类型。
decltype(f()) sum = x;
sum的类型时函数的返回类型,顶层const不会被忽略。引用从来都是作为其同义词出现,只有这里是例外(引用对象返回的就是引用类型) - 加括号是表达式,得到引用
decltype((i)) a
得到int&
类型
自定义数据结构
3- String, Vector, and Array
Primer第四版没有vector
,头文件不应该包含using
,因为头文件会被拷贝到引用它的文件中,会不经意包含了一些名字
string vector
- 不常用的初始化:
string s(n,'c')
连续n个c字符 - getline(is, s):从输入流读一行给s,换行符也读进去了,但是存到s中时没有存换行符。
- ?返回值?。segmentfault有回答过一个读一行的方法,连接
- string::size_type :size()方法的返回类型,为什么不是int呢?
- cctype 中的函数
迭代器
数组
- 复杂的定义从内向外阅读
int (* Parray) [10] = &arr; // Parray指向一个含有10个整数的指针
int (& arrRef) [10] = arr; // arrRef引用一个含有10个整数的指针
// 由内向外,首先arry是一个引用,
// 由右边:arry引用的对象是大小为10的数组
// 由左边:数组元素是指向int的指针
- 使用数组下标通常定义为
size_t
,与机器相关的无符号类型,能存放内存中任意对象的大小。定义在<cstddef>
中 - 像迭代器一样使用数组指针,获得尾后迭代器
int *end = &arr[n]
arr[n]是不存在的元素,但是可以获取地址,计算方式是arr右移n位C++11
在<iterator>
定义了begin() end()
函数,参数是数组指针
多维数组
4- Expressions
*pbeg++
等价于*(pbeg++)
解引用是原先pbeg指的值,pbeg右移一位后,返回初始值的副本。sizeof
运算符返回表达式或类型名字所占字节数。
sizeof (type)
sizeof expr // 返回表达式结果类型的大小,但并不计算其运算对象的值
- 隐式类型转换,算法表达式中,
unsigned int
和int
,int
会转化成unsigned int
- 显示转换:
static_cast dynamic_cast const_cast reinterpret_cast
5- Statements 语句
1 C++11
引用范围for语句(range for statement),
for (declaration : expression)
statement
// expression 是序列或者容器,declaration是变量,序列中每个元素都可以转化成declaration
// 用auto确保类型相容,最简单
// 如果要对元素进行读写,引用
vection<int> v={0,1,2,3}
for (auto &r : v){
r *= 2;
}
2 异常处理包括throw表达式、try语句块、一套异常类(用于传递throw和try子句信息)
6- Functions
- 函数定义和声明、参数传递和结果返回;
- 函数重载
- 特殊用途语言特性(inline、默认参数、constexpre函数)
- 函数匹配
- 函数指针
参数传递
- 传值参数
- 拷贝一份副本,不会影响原来实参的值
- 指针形参,拷贝的是指针的值,两个指针不同,但所指的对象相同,可以间接访问修改它所指的对象
- 传引用参数
- 引用实际上是作用在引用所引的对象上,引用形参类似,可以让函数修改实参的值
- 避免拷贝,变量不修改形参值时最好用常量引用
- 返回额外信息,把要返回的信息作为参数
- const形参和实参:顶层const在实参初始化时被忽略,所以 …
void fun(int);
void fun(const int); // error: 重定义
数组形参:作为指针传入,管理方式三种
- 数组本身包含一个标记,如C风格字符串的’\0’
使用标准库规范,传递数组首元素和尾后元素指针
void print(const int *beg, const int *end)
显示传递数组大小的形参
void print(const int ia[], size_t size)
数组引用形参
void prinf(int (&arr)[10]) { for (auto elem : arr) cout << elem << endl; }
main:处理命令行参数,
main(int argc, char **argv)
或(int argc, char *argv[])
- 可变形参
- 实参类型相同,传递标准库类型
initializer_list
- 实参类型不同,可变参数模板,在16.4节介绍
- 实参类型相同,传递标准库类型
- 不要返回局部对象的指针或引用,内存空间在函数结束用就被释放
返回数组指针,数组不能被拷贝,不能返回数组,但是可以返回数组的指针或引用。
类型别名
typedef int arrT[10]; // 类型别名,表示的类型是含有10个整数的数组 using arrT = int[10]; // 同上,等价 arrT* func(int i); // 函数返回一个纸箱含有10个int的数组的指针
直接声明
int (* func(int i)) [10]; // 由内向外阅读 func(int i) // 函数参数 (*func(int i)) // 可以对函数调用的结果进行解引用操作 (*func(int i))[10] // 表示解引用func的调用将得到一个大小是10的数组 int (* func(int i)) [10]; // 表示数组元素是int
尾置返回类型
C++11
auto func(int i) -> int(*)[10];
使用decltype
已经确定函数返回的指针将指向哪个数组
int odd[]={1,3,5}; int even[]={2,4,6}; decltype(odd) *arrPtr(int i){ // decltyep 返回的是数组,没有转化成指针 return (i%2)? &odd : &even; }
函数重载
- 名字相同,形参列表不相同(编译器根据实参类型推断使用的函数
- 名字和形参相同,返回类型不同的不是重载,引起重定义错误
- 顶层const会被忽略
const_cast:有两个函数版本,一个参数是const引用,一个不是;可以在非常量引用版本里强制将参数转化成const,然后调用const函数版本
const string &func (const string &str); // 版本1 string &func (string &str){ // 版本2 auto &r = func(const_cast<const string&> str) return const_cast<string&> (r); }
调用函数重载,三种可能结果
- 找到一个实参最佳匹配
- 错误:无匹配
- 错误:有多于一个函数可以匹配,但都不是最佳匹配,二义性选择
void func(int, int); void func(double, double); // 函数调用 func(1, 2.1); // 二义性错误,见函数匹配一节
- 重载与作用域,如果外面有一个函数名func,局部有变量int func,局部作用域内int func会覆盖掉函数;但是若在局部声明函数后,函数名func会覆盖掉变量int func
特殊用途语言特性(inline、默认参数、constexpre函数)
默认参数,不能修改已有默认参数值,但声明可以添加默认参数
void func(int x,int y=1); // 原来的 void func(int x=1,int y); // 正确 void func(int x,y=2); // 错误,不能添加默认实参
constexpr函数:返回类型及参数都是常量,函数体只有一条return语句
constexpr int func(int cnt) { return 3*cnt; } // 返回值可以不是常量 // 使用时,传进去的实参cnt要const类型
- 调试帮助
要屏蔽掉调试代码,两种与处理功能:assert和NDEBUG
assert(expr)
expr为假则程序终止运行,用于检查不能发生的条件- NDEBUG预处理变量
如果用#define定义了NDEBUG,则assert不执行运行检查,默认是执行的 - C++定义的有助于调试的变量(字符串字面值)
__func__
每个函数都有,函数名__FILE__
文件名__LINE__
行号__TIME__
编译时间__DATE__
编译日期
函数匹配
- 步骤1:选定重载函数集,集合中的是候选函数 (函数名相同、声明在调用点可见)
- 步骤2:考察实参,选出可行函数 (形参数量和实参相等,类型匹配或可转换)
- 步骤3:寻找最佳匹配,如果没有任何一个函数脱颖而出,则引发二义性调用的错误,如上述默认参数的例子
- 对每个实参的匹配都不劣于其他可行函数
- 至少有一个实参的匹配优于其他函数
实参类型转换,优先级:
- 精确匹配
- 类型完全相同
- 实参从数组或函数类型转换成对应的指针类型
- 向实参添加或删除顶层const
- 通过cosnt转换
- 类型提升
- 算术类型转换或指针转换
- 类类型转换
函数指针
声明:直接用指针代替函数名即可
bool (*func)(int); // 未初始化
使用
将函数名作为一个值使用时自动转换成指针pf = &func; // func() 是函数,参数和pf的参数必须精确匹配 pf = func; // 调用函数 pf(); (*pf)();
- 作为形参
- 作为函数返回值
- 将auto和decltype用于函数指针类型
7- Class
- 定义抽象数据类型
- this 是一个常量指针,不能改变this保存的地址
- 成员函数参数列表后紧跟的const表示this是一个指向常量的指针,该成员函数被称作常量成员函数
- 常量对象、常量对象的引用或指针只能调用常量成员函数
- 用 =default 显示声明默认构造函数
- 访问控制与封装
其他特性
- mutable ,可变数据成员,即使在const对象内也能被修改
- 类内初始值,用=初始化
- 友元
- 类之间的友元关系
- 成员函数作为友元,要明确指出该成员函数属于哪个类
class A{ friend void B::func()} // 先声明B,声明函数,定义函数
- 函数重载和友元,要把一组重载函数作为友元,每一个函数都要分别声明
- 友元声明和作用域
- 作用域
构造函数
- 初始值列表:根据定义的顺序初始化
- 防止构造函数隐式转换,explicit,只对一个实参的构造函数有效,只能在类内部定义;只能用于直接初始化,不支持拷贝(=)形式的初始化
- 聚合类(aggregate) 用户可以直接访问成员,struct,要求
a. 所有成员public
b. 没有定义任何构造函数
c. 没有类内初始值
d. 没有基类,没有virtual函数 - 字面值常量类
静态成员
- 类的静态成员存在于任何对象之外,对象中也不包含任何静态成员相关数据
- 类似的,静态函数不与任何对象绑定,不包含this指针,不能声明为const(const是对this的修饰),函数内不能使用const;
- 必须在类的外部定义和初始化成员静态成员,static只在类内部声明,在外部定义不需要
- 可以是public或private,可以是常量、引用、指针
-如何让一段程序崩溃? 给空指针赋值
-疑问:调试时添加打印语句,打印语句应该一直保持刷新流,否则如果程序崩溃,输出还可能留在缓冲区,导致崩溃位置的错误推断??
—————————记事—————————
啊哈哈哈隔了好几天断断续续,还写完了
之前断断续续看Primer,用脑袋编译哈哈,这次准备敲代码了。
使用mingw g++编译