Basic Syntax¶
约 2585 个字 14 行代码 预计阅读时间 9 分钟
Initialization¶
初始化是指在创建对象(为特定类型的对象申请存储空间)的同时赋初始值。C++ 的初始化方式与规则五花八门,大概包括以下几种:直接初始化、拷贝初始化、列表初始化、默认初始化、值初始化、类内初始值、构造函数初始值列表。
一般有着四类初始化方式,现代 C++ 的内置类型和类类型都支持这四种初始化方式:
- 等号:
int a = 1;
或者std::string s = "hello";
- 等号加花括号:
int a = {1};
或者std::string s = {"hello"};
- 花括号:
int a{1};
或者std::string s{"hello"};
- 圆括号:
int a(1);
或者std::string s("hello");
-
默认初始化/Default Initialization:当对象未被显示地赋予初值时执行的初始化行为。
- 当在块作用域内不实用任何初始值定义一个非静态变量的时候,使用默认初始化;
- 当一个类本身含有类类型的成员并且使用合成的默认构造函数的时候,使用默认初始化;
- 当类类型的成员没有在构造函数初始值列表内显式地初始化的时候,使用默认初始化;
- 类类型:由类的默认无参构造函数决定,如果没有默认无参构造函数,则该类不支持默认初始化。
- 内置类型(指针、
int
、double
、float
、bool
、char
等)及其数组:- 全局(包括定义在任何函数之外、命名空间之内的)变量或局部静态变量:初始化为 0(这种情况也叫值初始化);
- 局部非静态变量或类成员:未定义(未初始化)。
-
值初始化/Value Initialization:默认初始化的特殊情况,此时内置类型会被初始化为 0。基本场景:
- 当 STL 容器只指定元素数量,而不指定初值时,就会执行值初始化,如
vector<int> vec(10);
:10 个int
,初始化为0
; - 当 STL 容器指定元素数量,但是提供的初始值少于数组的大小的时候,剩下的元素会被值初始化;
- 当我们不实用一个初始值定义一个局部静态变量的时候,就会执行值初始化;
- 全局(包括定义在任何函数之外、命名空间之内的)变量被值初始化为 0;
new
类型,后面带括号,如:new int(), new string{};- 初始值列表为空
{}
,如double d{};
、int *p{};
。
对于类类型,其实不需要区分默认初始化和值初始化,因为类类型的初始化只决定于构造函数,与对象在函数内/外、全局/局部/类成员、静态/非静态、默认初始化/值初始化无关。
- 当 STL 容器只指定元素数量,而不指定初值时,就会执行值初始化,如
Iterator¶
Functions¶
1 Basics¶
一个典型的函数包括:返回类型、函数名字、形参列表与函数体。函数也需要像其余的名字一样在使用前声明,函数声明也称为函数原型/Function Prototype。使用调用运算符来执行函数,调用运算符的形式是一对圆括号 ()
,运算符作用于一个表达式,这个表达式是函数或者指向函数的指针,调用表达式的类型就是函数的返回类型。函数的返回类型不能为数组类型或者函数类型,但可以是指向数组或者函数的指针。
函数的调用其实完成了两项工作:一个是使用实参初始化函数对应的形参,另一个是将控制权从主调函数转移到被调函数。而返回语句也完成两项工作:一个是返回 return
语句中的值,另一个是将控制权从被调函数转移到主调函数。
当形参列表为空的时候,可以直接书写一个空的形参列表,这样就隐式定义了空形参列表,但是与 C 语言兼容的形式是显式使用 void
关键字。
在 C++ 中,名字有作用域,对象有生命周期/Lifetime,作用域是程序文本的一部分,名字在作用域中可见,对象的生命周期是程序执行过程中对象存在的一段时间。
函数体是一个语句块,块构成一个新的作用域。函数的形式参数和函数体内部定义的变量统称为局部变量,局部变量只有在函数的作用域内可见,并且会隐藏在外层作用域中同名的其他声明。所有在函数体外定义的对象存在于程序的整个执行过程之中,在程序启动时被创建,直到程序结束才会被销毁,局部变量的的生命周期依赖于定义的方式:
- 自动对象/Automatic Object:只存在于块执行期间的对象。当函数的控制路径经过变量定义语句的时候创建该对象,当到达定义所在的块末尾的时候销毁它,当块执行结束后,块中创建的自动对象的值就变成未定义的了。形参就是一种自动对象,在函数开始的时候对形参申请存储空间,函数终止就被销毁。有初始值的时候,自动对象的值就是初始值,没有初始值的时候,自动对象就执行默认初始化。
- 局部静态对象/Local Static Object:在程序的执行路径第一次经过对象定义语句的时候初始化,并且知道程序终止的时候才会被销毁,在此期间即使对象所在的函数/块结束执行也不会对其有影响。将局部变量定义为
static
类型就可以得到这样的对象。当局部静态对象没有初始值的时候执行值初始化。
C++ 与现代编译器支持分离式编译/Separate Compilation。
2 Argument Passing¶
5 Features for Specialized Uses¶
5.1 Default Argument¶
很多函数有这样一种形参,在函数的很多次调用中都被赋予一个相同的值,我们将这个反复出现的值成为函数的默认实参/Default Argument,调用含有默认实参的函数时,可以包含这个实参,也可以省略该实参。默认实参只能出现在形参列表的尾部,一个形参一旦被赋予了默认实参,它后面的所有形参都必须有默认实参。在给定的作用域中,一个形参只能被赋予一次默认实参,函数的后续声明都只能为之前那些没有默认值的形参提供默认实参。
对于已经有默认实参的函数 std::string screen(size_t ht = 24, size_t wid = 80, char backgrnd = ' ')
,函数调用时实参按照其位置解析,默认实参负责填补函数调用缺少的尾部实参。若想要覆盖中间的某个默认实参(如 wid
),就需要为前面的所有形参提供实参。
局部变量不可以作为默认实参,除此之外,只要表达式的类型可以转换成形参所需要的类型,就可以作为默认实参。默认实参的名字在函数声明所在的作用域内解析,名字的求值是在函数调用时进行的,而不是在函数声明时。
5.2 Inline Function and constexpr
Function¶
定义在类内的成员函数会被编译器隐式地视为内联函数,但是是否真正内联由编译器决定。
5.3 Debugging¶
7 Pointers to Functions¶
首先需要说明:函数指针指向的是函数而非对象,函数指针指向某种特定类型,函数的类型由它的返回类型和形参类型共同决定,与函数名无关。函数指针的声明形式为:return-type (*pointer-name)(parameter-list)
,其中 return-type
是函数的返回类型,parameter-list
是函数的形参列表,pointer-name
是函数指针的名字。
若我们定义如下的函数 bool lengthCompare(const string &, const string &);
,函数指针的声明形式为:bool (*pf)(const string &, const string &);
。有以下几点需要注意:
- 当我们将函数名作为一个值使用时,该函数自动转换成指针:
pf = lengthCompare;
,当然使用取地址运算符也是允许的:pf = &lengthCompare;
,这两种复制方式等价; - 调用函数可以直接使用函数指针,不需要解引用:
bool b1 = pf("hello", "goodbye");
,当然解引用也是允许的:bool b2 = (*pf)("hello", "goodbye");
,这两种调用方式等价; - 在指向不同函数类型的指针间不存在转换规则,但是可以为函数指针赋一个
nullptr
值,或者值为0
的常量表达式,表示该指针没有指向任何函数。 - 当使用重载函数的时候,上下文必须清楚界定到底是使用了哪个函数,指针类型必须与重载函数中的某一个精确匹配,例子如下:
- 虽然不能定义函数类型的形参,但是形参可以是函数指针,这时,虽然形参看起来是函数类型,但是实际上却当成是函数指针类型使用,下面两个声明是等价的:
我们可以直接将函数作为实参使用,其将自动转换成指针:
useBigger(s1, s2, lengthCompare);
。 - 函数指针也可以作为返回值,但是我们必须将返回类型写成指针形式,编译器不会自动将函数转换成指针,一般有下边三种写法:
- 使用类型别名:
using F = int(int *, int); F *f1(int);
,这里定义的F
是函数类型,函数不能直接返回函数类型,还可以使用using PF = int(*)(int *, int); PF f1(int);
; - 直接强行声明:
int (*f1(int))(int *, int);
,这里要从里往外读:f1
有形参列表,所以是个函数,并且应该返回一个指针,这个指针的类型也包括形参列表,所以是一个函数指针; - 尾置返回类型:
auto f1(int) -> int (*)(int *, int);
,这种写法更加直观。
- 使用类型别名:
typedef
、decltype
和上面的using
一样,也区分函数类型与函数指针类型: