指针或引用的定义方法

指针有两种定义写法:

// * 靠近变量
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

初始化就是在变量在构造时提供其初值. 如果是拷贝初始化的话, 看起来跟赋值很像, 但其实二者不同.

  1. 初始化其实只进行了一次操作, 即在声明的同时即赋予了值, 没有临时量的存在.
  2. 赋值操作是两次操作, 即先创建一个临时量, 然后将这个临时量赋予变量.

默认初始化

// 这是在不使用初始化器构造变量时执行的初始化
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];
Comments
Write a Comment