C++ 11基本语法
单词 ——> 表达式 ——> 语句 ——> 函数 ——> 类
1 变量与类型
数据类型决定了程序中的数据和操作的意义。
内置类型体现了硬件本身的能力特性。
1.1 基本数据类型(内置)
1.1.1 类型的表示范围
- 溢出:
unsigned char c = -1 // c的值为255
1.1.2 常量(字面量literal)
- 整型和浮点型字面值
- 默认十进制字面值是带符号数
- 0开头的整数表示八进制数
- 0x开头的代表十六进制数
- 字符和字符串字面值
- 转义
- 布尔字面值:true,false
- 指针字面值:nullptr
1.2 变量
1.2.1 变量初始化
- 默认初始化
- 值初始化
1.2.2 变量声明与定义
一条声明语句由一个基本数据类型(base type)和紧随其后的一个声明符(declarator)列表组成。
1.2.3 什么是对象
对象(object):通常指一块能存储数据并具有某种类型的内存空间。
1.3 复合类型
1.3.1 引用(reference)
引用为对象起了另外一个名字,引用类型引用另外一种类型。通常将声明符写成 &variable
的形式来定义引用类型。
引用的类型必须与其所引用对象的类型一致。但有两个例外:
- 在初始化常量引用时允许用任意表达式作为初始值,只要该表达式的结果能转换成引用的类型即可。
1.3.2 指针(pointer)
指针是指向另外一种类型的复合类型。实现了对其他对象的间接访问。
- 获取对象的地址
- 指针值
- 指向一个对象
- 指向紧邻对象所占空间的下一个位置
- 空指针
- 无效指针
-
利用指针访问对象
-
空指针
-
void*
指针 -
指向指针的指针
int **ppi = &pi
-
指向指针的引用
int *&r = p
因为引用本身不是一个对象,所以不能定义指向引用的指针。
指针的类型必须与其所指对象的类型一致。但有两个例外:
- 允许令一个指向常量的指针指向一个非常量对象。
1.4 const限定符
关键字const
对变量的类型加以限定。
1.4.1 const对象
- 默认情况下,const对象仅在文件内有效
1.4.2 对const[对象]的引用
常量引用
1.4.3 指针与const
- 指向常量[对象]的指针
- 常量指针
1.4.4 顶层const与底层const
- 指针类型既可以是顶层const也可以是底层const
1.4.5 constexpr
- 常量表达式
1.5 对类型的处理
1.5.1 类型别名
- typedef:关键字
typedef
作为声明语句中的基本数据类型的一部分 - 别名声明:
using SI = Sales_item
- 类型别名指代复合类型或常量时,特别注意。
1.5.2 类型说明符auto
- auto定义的变量必须有初始值
1.5.3 类型指示符decltype
- 返回操作数的类型,并不实际计算表达式的值
- decltype与引用
1.6 自定义数据结构
1.6.1 struct
1. struct Sales_data {
}trans,*salesptr;
2. struct Sales_data {
};
Sales_data trans,*saleptr;
1.6.2 class
- 抽象数据类型
1.6.3 预处理器
确保头文件多次包含但仍能安全工作的常用技术是预处理器。
- 功能一:
#include
- 功能二:头文件保护符,如
#ifndef... #define... #endif
1.7 标准库类型string
1.7.1 定义与初始化
- 直接初始化
1.7.2 操作
- 读写
- empty和size
- 处理字符
1.8 标准库类型vector
1.8.1 定义与初始化
- vector模板控制着定义与初始化向量的方法
1.8.2 操作
push_back
- 下标索引
1.8.3 迭代器iterator
- 获取迭代器,
begin
,end
- 迭代器运算
- 迭代器失效
1.9 数组
1.9.1 定义与初始化内置数组
- 显示初始化
- 初始化列表
1.9.2 指针与数组
- 指针也是迭代器
1.9.3 多维数组
- 严格来说,C++没有多维数组。
2 表达式
2.1 基础概念
- 左值与右值
- 优先级与结合律
2.2 运算符
2.2.1 算术运算符
- 溢出
- 取模&取余
2.2.2 逻辑与关系运算符
- 短路求值
2.2.3 赋值运算符
- 复合赋值
2.2.4 递增与递减运算符
- 使用前置版本
2.2.5 成员访问运算符
- 点运算符
- 箭头运算符
2.2.6 条件运算符
condition ? expr1 : expr2
2.2.7 位运算符
- bitset
2.2.8 sizeof
sizeof (type)
sizeof expr
2.2.9 逗号运算符
- 通常用于for循环中
2.3 类型转换
2.3.1 隐式类型转换
隐式类型转换的时机:
- 算术隐式类型转换
- 条件中,非布尔值转换成布尔类型
- 初始化过程中,初始值转换成变量的类型;赋值语句中,右侧运算对象转换成左侧运算对象的类型
- 如果算术运算或关系运算的运算对象有多种类型,需要转换成同一类型
- 函数调用
- 其他
2.3.2 显式类型转换
- 格式:
cast_name<type>(expression)
- static_cast
- dynamic_cast
- const_cast:只能改变运算对象的底层const,常用于函数重载中。
- reinterpret_cast
3 语句
- 简单语句
- 空语句,如
;
- 复合语句:用花括号括起来的语句和声明的序列。复合语句也被称为块,一个块就是一个作用域。
3.1 条件语句
if-else if-else
switch...case
3.2 迭代语句
do...while
while
for
3.3 跳转语句
break
continue
3.4 try语句块与异常处理
throw
try...catch
4 函数
- 函数:函数定义一般包括返回类型、函数名字、形参列表和函数体。
- 局部对象
- 局部变量:形参和函数体内部定义的变量统称为局部变量,仅在函数的作用域可见,也会隐藏在外层作用域中同名的其他声明
- 自动对象:只存在于块执行期间的对象,如形参
- 局部静态对象:在程序执行路径第一次经过对象定义时初始化,生命周期与程序一致
- 函数声明(函数原型):函数的三要素<返回类型,函数名,形参列表>描述了函数的接口,函数只能定义一次,但是可以声明多次。
- 分离式编译
4.1 参数传递
- 形参初始化机理与变量初始化一样。
- 形参的类型决定了形参与实参交互的方式
- 形参是引用类型时:它对应的实参被引用传递,函数被传引用调用
- 形参是值类型时:实参的值被拷贝给形参,形参和实参是两个相互独立的对象。实参被值传递,函数被传值调用
4.1.1 传值参数
- 普通值参数:拷贝实参的值给普通非引用形参
- 指针形参:执行指针拷贝操作,拷贝指针的值给指针形参
4.1.2 传引用参数
- 对于大的类类型对象或者容器对象来说,拷贝比较低效甚至不支持拷贝操作,如IO类型。这时使用引用避免拷贝。
- 当函数无需修改引用形参的值时最好使用常量引用。
- 可以使用引用形参返回额外信息。
4.1.3 const形参与实参
- 当用实参初始化形参时会忽略掉形参的顶层const。当形参有顶层const时,传给它常量对象或者非常量对象那个都是可以的。
- 指针或引用形参与const
- 可以使用一个非常量初始化一个底层const对象,但是反过来不行
- 一个普通的引用必须用同类型的对象初始化
4.1.4 数组形参
- 数组的特殊性质
- 不允许拷贝数组
- 使用数组时通常会将它转为指针
4.2 返回类型
4.2.1 无返回值函数
return;
4.2.2 有返回值函数
- 不要返回局部对象的引用或指针
- 列表初始化返回值。
return {...}
4.2.3 返回数组指针
- 使用尾置返回类型。
auto func(int i) -> int(*)[10]
- 使用decltype
4.3 函数重载
如果统一作用域内的几个函数名字相同但形参列表不同,则称之为重载函数。
4.3.1 判断形参类型是否相异
- 类型别名:类型别名仍然是同一类型。
- const形参:顶层const不影响传入函数的对象。如果形参是某种类型的引用或指针,则底层const需要推断是否是常量对象。
4.3.2 const_cast与重载
const string &shorterString(const string &s1,const string &s2) {
reutrn s1.size() <= s2.size() ? s1 : s2;
}
string &shorterString(string &s1,string &s2) {
auto &res = shorterString(const_cast<const string&>(s1),
const_cast<const string&>(s2));
return const_cast<string&>(res);
}
4.3.3 函数匹配(重载确定)
- 候选函数
- 与被调用的函数同名
- 声明在调用点可见
- 可行函数
- 形参数量与本次调用提供的实参数量相同
- 每个实参的类型与对应的形参类型相同或者是可以转换成形参的类型
- 最佳匹配
- 该函数每个实参的匹配都不劣于其他可行函数需要的匹配
- 至少有一个实参的匹配优于其他可行函数提供的匹配
- 实参类型转换
4.3.4 作用域与重载
- C++中,名字查找发生在类型检查之前。
4.4 函数指针
函数指针指向的是函数而不是对象。函数指针指向某种特定类型,函数的类型由它的返回类型和形参类型决定,与函数名无关。
bool lengthCompare(const string &,const string &);
bool (*pf)(const string &,cosnt string &);
4.5 特殊用途
4.5.1 默认实参
某些函数有这样一种形参,在函数的很多次调用中都被赋予相同的值,此时,可以将这个反复出现的值成为函数的默认实参。
- 调用含有默认实参的函数时,可以包含该实参,也可以省略该实参。
- 局部变量不能作为默认实参。
4.5.2 内联函数
将函数声明为inline
,通常就是将它在每个调用点上“内联”地展开,可以避免函数调用的开销。
4.5.3 constexpr函数
能用于常量表达式的函数。
- 函数的返回值类型及所有形参的类型必须时字面值类型。
- 函数体中必须有且仅有一条return语句。
4.5.4 assert
- 预处理宏:
assert
宏定义在cassert
头文件中,由预处理器而不是预编译器管理。】 NODEBUG
5 类
5.1 类基础概念
5.1.1 抽象数据类型
类的基本思想是数据抽象与封装。要想实现这两点,需要首先定义一个抽象数据类型。
- 数据抽象:一种依赖于接口和实现分离的编程(和设计)技术。
- 接口:用户能执行的操作
- 实现:类的数据成员,接口实现的函数,定义类所需的各种私有函数
- 封装:实现了类的接口与实现的分离。
5.1.2 访问控制
在C++中,使用访问说明符来加强类的封装性。
public
:定义在public说明符后的成员在整个程序内可被访问,public成员定义类的接口。private
:可以被类的成员函数访问,封装(隐藏)了类的实现细节。
class与struct的对比:默认访问权限不同
class
:定义在第一个访问说明符前的成员是private的struct
:定义在第一个访问说明符前的成员是public的
5.1.3 拷贝赋值析构
除了定义类的对象如何初始化,类还需要控制拷贝、赋值和销毁对象时发生的行为。如果不主动定义这些操作,编译器将默认合成这些操作。
但是对于某些类来说,这些合成的版本可能无法正常工作。如分配和管理动态内存的类。不过,很多需要动态内存的类能使用vector对象或者string对象管理必要的存储空间,使用vector和string的类能避免分配和释放内存带来的复杂性。
5.1.4 作用域与名字查找
一个类就是一个作用域。在类的作用域外,普通的数据和函数成员只能由对象、引用或指针使用成员访问运算符来访问。对与类类型成员则使用作用域运算符访问。
5.2 类的成员
5.2.1 类型名成员
类可以自定义某种类型在类中的别名。如using pos = std::string::size_type
或typedef std::string::size_type pos
。
- 一般来说,内层作用域可以重新定义外层作用域中的名字。但是对于类型名成员来说,如果成员使用了外层作用域中的名字,而该名字代表一种类型,则类不能在之后重新定义该名字。
5.2.2 可变数据成员
如果希望修改类的某个数据成员,即使是在一个const成员函数内。可以通过在变量声明中加入mutable
关键字实现。
一个可变数据成员不会是const,即使它是const对象的成员。
5.2.3 类类型成员
可以把类名作为类型的名字使用,从而直接指向类类型。如Sales_data item
或class Sales_data item
。
- 前向声明:可以仅仅声明类而暂时不定义它。如
class Screen
,对于类型Screen
来说,在它声明之后定义之前是一个不完全类型。 - 不完全类型:只能在非常有限的情况下使用,可以定义指向该类型的指针或引用,也可以声明,但是不能定义以不完全类型作为参数或者返回类型的函数。
5.2.4 友元成员
通过引入友元函数或者友元类来控制外部类对私有成员的访问权限。
5.2.5 函数成员
- 构造函数
- 普通成员函数
- 内联成员函数
- const常量成员函数
5.2.6 静态成员
- 静态成员与类本身直接相关,而不是与类的各个对象保持关联。
- 声明:静态数据成员的类型可以是常量、引用、指针、类类型等。
- 使用:使用作用域运算符访问静态成员。
- 定义:static关键字只出现在类内部的声明语句,不需要在外部定义时重复
5.3 构造函数
每个类定义了它的对象被初始化的方式,这些控制初始化过程的函数被称为构造函数。构造函数的任务是初始化类对象的数据成员,只要类的对象被创建,就会执行构造函数。
- 构造函数名字与类名相同
- 构造函数没有返回值
- 构造函数不能是const的
5.3.1 合成的默认构造函数
当没有声明构造函数时,编译器会合成一个默认的构造函数来执行初始化过程,可能是默认初始化或者用类内的初始值来初始化数据成员。如Sales_data() = default;
某些类不能依赖于默认构造函数:
- 如果一个类在某种情况下需要控制对象初始化,那么该类很可能在所有情况下都需要控制
- 如果定义在块中内置类型或复合类型的对象被默认初始化,则它们的值将是未定义的
- 如果类中包含一个其他类类型的成员,且该成员没有默认构造函数,那么编译器将无法初始化该成员
默认构造函数的作用:当对象被默认初始化或值初始化时自动执行默认构造函数。
默认初始化发生时机:
- 在块作用域中不使用任何初始值定义一个非静态变量或数组时
- 当一个类本身含有类类型的成员且使用合成的默认构造函数时
- 当类类型的成员没有在构造函数初始化列表中显式地初始化时
值初始化发生时机:
- 在数组初始化过程中,提供的初始值数量小于数组大小时
- 不使用初始值定义一个局部静态变量时
T<>
形式的表达式显式地请求值初始化时
5.3.2 构造函数初始值列表
- 如果成员是const、引用或者属于某种未提供构造函数的类类型,必须通过构造函数初始值列表为这些成员提供初值。
- 成员初始化顺序:构造函数初始值列表只说明用于初始化成员的值,而不限定初始化的具体顺序,顺序由成员在类中定义的顺序决定。但如果用一个成员来初始化另一个成员,需要注意顺序与编译错误。
5.3.3 默认实参构造函数
class Sales_data {
public:
Sales_data() = default;
Sales_data(std::string s = ""): bookNo(s) {
}
};
两种方式的唯一区别是:
- 默认构造函数:隐式地使用了string的默认构造函数初始化bookNo
- 默认实参构造函数:使用实参s初始化bookNo
5.3.4 委托构造函数
- 委托构造函数:使用它的所属类的其他构造函数执行自己的初始化过程
- 在委托构造函数内,成员初始值列表只有一个唯一入口,即类名本身
5.3.5 constexpr构造函数
- 聚合类
- 字面值常量类
- 字面值常量类的构造函数可以是constexpr函数
5.3.6 隐式的类类型转换
转换构造函数:如果构造函数只接受一个实参,则它实际上定义了转换为此类类型的隐式转换机制。
- 只允许一步类类型转换
- 抑制构造函数定义的隐式转化:
explicit
关键字,只能用于直接初始化 - 显式使用转换函数来进行转换
class Sales_data {
public:
Sales_data(const std::string &s): bookNo(s) {
}
Sales_data(std::istream&);
explict Sales_data(const std::string &s): bookNo(s) {
}
explict Sales_data(std::istream&);
Sales_data &combine(const Sales_data&);
};
string null_book = "9-999-999-9999";
Sales_data item;
1. item.combine(Sales_data(null_book));
2. item.combine(static_cast<Sales_data>(cin));