指针或引用的定义方法
指针有两种定义写法:
// * 靠近变量
int *p_type1;
// * 靠近类型
int* p_type2;
这两种定义方法其实是等效的, 但是我个人现在喜欢使用第二种写法, 即 *
靠近类型, 这样变量名就是变量名, 前面的才是它的类型(比如 int*
), 意味着是指向 int
型的指针.
引用同理, 我会定义成 int& ref
. 这同时也是《C++ Premier》里建议的定义方式.
顶层 const & 底层 const
基于上面的说明, 类型定义的时候统一写在一起, 远离变量名, 在定义含有 const 的复合类型的时候, 可以有如下的定义方式:
// 方式一, 我现在不建议这样使用, 与方式二等效
const int* p_type1;
// 方式二, 与方式一等效, 我个人建议使用, 此处属于底层 const
int const* p_type2;
// 方式三, 此处属于顶层 const
int *const p_type3;
所谓顶层 const
, 就是自己该指针一定确定指向某一地址后, 不能更改. 所谓底层 const
, 就是该指针指向的是一个不能更改的变量, 也即常量.
判断顶层和底层根据 const
和 *
强行记忆位置强行记忆: const 在右为顶层 const, const 在左为底层 cost.
由于引用只可能指向一个对象, 不能中途变更, 其实也就是天生顶层 const
, 所以理论上只有如下写法:
// 正确, 指向常量的引用
int const& ref_1;
// 错误, 没有这种写法
int &const ref_2;
函数指针 & 指针函数
函数指针: 指向函数的指针
指针函数: 返回指针的函数
// 函数指针
// pf 是指针, 指向一个返回值是 bool, 参数为空的函数
bool (*pf)();
// 指针函数
// foo 是一个函数, 参数列表为空, 返回的是 bool* 型 (返回指向 bool 型的指针)
bool* foo();
记忆方法, 参考最上面指针定义写法的记载, 建议 *
和前面的类型写在一起.
那么不加括号的情况下, *
天然和 bool
就写在一起了, 所以很容易看出来 foo
是函数, 返回的是 bool*
类型.
而我们在 *pf
两端加上括号后, 也很容易看出来首先它返回的是一个 bool
类型, 又有 *
的存在, 那么它只能是指针类型了, 指向的是返回 bool
类型的函数.
复杂的数组声明
// 存放指针的数组
int* ptrs[10];
// 指向数组的指针
int (*arr)[10];
// 错误写法, 不存在存放引用的数组. 强行记忆
int& refs[10];
// 引用一个数组
int arr[10] = {};
int (&aref)[10] = arr;
记忆方法, 参考上面函数相关的记忆.
不加括号的情况下, *
天然和 int
就写在一起了, 所以很容易看出来 ptrs
数组名, 存放的是 int*
, 其中可以存放 10 个数据.
加括号的情况, *arr
肯定就是指针了, 指向的类型可以看出指向的是数组, 数组中存放的是 int
类型, 因为 int
后没有紧跟 *
, 该数组可以存放 10 个数据.
引用的情况类似. 不过需要强行记忆 不存在存放引用的数组.
数组和函数的复杂定义
首先, 因为数组不能被拷贝, 所以函数不能返回数组, 也就是说我们不能定义返回数组的函数.
但是, 我们可以定义返回数组的指针或引用的函数.
也可以定义返回指向函数的指针的函数.
简单的说, 就是可以定义返回各种指针的函数.
另外, 我们也可以定义存放各种函数指针的数组.
记忆方法就是从右向左, 由内而外, 括号优先.
// 返回数组的指针的 **函数**
// 首先, foo() 就说明了它是一个函数, 剩下的都是它的返回值
// 返回的类型是 int (*)[10], 说明返回的是一个指针, 指向的是一个 int [10] 的数组
int (*foo())[10];
// 返回函数指针的 **函数**
// 首先, foo() 说明它是一个函数, 剩下的都是它的返回值
// 返回的类型是 int (*)(), 说明它返回的是一个指针, 指向的是一个 int ()
// 意思是该指针指向一个返回 int 型的函数
int (*foo())();
// 存放函数指针的 **数组**
// 首先, a[10] 说明它是一个数组, 可以存放 10 个数据
// 然后, 存放的类型是 int (*)(), 说明存放的是一个指针
// 该指针指向的是一个返回 int 型数据的函数
int (*a[10])();
size_type
string::size_type
代表 string 容器中两个元素间的距离.
array<int, 10>::size_type
代表 array 容器中两个元素间的距离.
auto 与引用
参与初始化的是对象的值, 在使用 auto
推导一个引用对象的值的时候, 参与初始化的不是一个引用, 而是这个引用的值, 所以 auto
无法获得一个引用:
int &foo;
auto not_ref = &foo; // not_ref 的类型是 int, 而非 int&
如果想要通过 auto
获得一个引用, 则需要显式的表示出来:
int &foo;
auto &ref = &foo; // 因为显式的注明了, 所以 ref 的类型成为了 int&
各种初始化
参考链接: https://zh.cppreference.com/w/cpp/language/initialization
初始化就是在变量在构造时提供其初值. 如果是拷贝初始化的话, 看起来跟赋值很像, 但其实二者不同.
- 初始化其实只进行了一次操作, 即在声明的同时即赋予了值, 没有临时量的存在.
- 赋值操作是两次操作, 即先创建一个临时量, 然后将这个临时量赋予变量.
默认初始化
// 这是在不使用初始化器构造变量时执行的初始化
new T;
值初始化:
std::string s{};
// 这是在变量以空初始化器构造时进行的初始化
// 一般是空括号或花括号组成的初始化器
T();
new T();
T{};
new T{};
直接初始化
// 从构造函数参数的显式集合初始化对象
std::string s("hello");
复制初始化
// 从另一对象初始化对象
std::string s = "hello";
列表初始化
// 从花括号初始化器列表初始化对象
std::string s{'a', 'b', 'c'};
聚合初始化
// 从花括号初始化器列表初始化聚合体
// 聚合体包括 1. 数组类型 2. 类类型
char a[3] = {'a', 'b'};
引用初始化
// 绑定引用到对象
char& c = a[0];