Reading Note of CppPrimer-Chapter6
Functions
Function Basic
函数是一个命名了的代码块 (block);一个函数一般由函数名、返回值类型、0 个或者多个形参以及函数体构成;通过函数调用运算符
()
来执行函数;函数的调用主要完成两项工作,使用实参初始化形参、主调函数 (calling function) 执行被中断、被调函数 (called function) 开始执行永远用不到的函数,可以有声明而没有定义 (即不完整);类似于变量,函数可以多次声明,一次定义 (inline 和 constexpr 函数除外);
可以在任何作用域对函数进行声明;在类作用域内声明的函数可以是类的成员函数或者友元;
函数声明有两种形式
1
2noptr-declarator ( parameter-list ) cv(optional) ref(optional) except(optional) attr(optional)
noptr-declarator ( parameter-list ) cv(optional) ref(optional) except(optional) attr(optional)-> trailingparameter-list: possibly empty, comma-separated list of the function parameters
cv: const/volatile qualification, only allowed in non-static member function declarations
ref: (since C++11) ref-qualification, only allowed in non-static member function declarations,indicate the function can be called by lvalue(&) or rvalue(&&)
except: the exception specification is not part of the function type, can be dynamic exception specification or noexcept specification
attr: These attributes are applied to the type of the function, not the function itself. The attributes for the function appear after the identifier within the declarator and are combined with the attributes that appear in the beginning of the declaration, if any.
1
2
3
4[[noreturn]] void f [[noreturn]] (); // okay: both attributes apply to the function f
// However, the attributes that appear after the declarator (in the syntax above), apply to the type of the function, not to the function itself
void f() [[noreturn]]; // error: this attribute has no effect on the function itself
函数声明可以和其他变量声明融合在一行
1
2// declares an int, an int*, a function, and a pointer to a function
int a = 1, *p = NULL, f(), (*pf)(double);C++11 起可以声明函数是删除的 (deleted);如果删除的函数有重载函数,则优先匹配重载的非删除的函数;任何形式的调用删除的函数 (deleted function) 都是非法的 (ill-formed);一个函数被声明为删除的,必须在当前编译单元第一次声明这个函数的时候就声明为删除的;
Parameters & Arguments
形参 (parameters):形参是定义在函数作用域中的一种局部变量,和定义在函数块内部的局部变量一样,都是局部变量;函数开始时为形参申请存储空间,一旦函数执行完毕,形参就被销毁;
尽管实参和形参存在对应关系,但是并没有规定实参 (argument) 的求值顺序;即顺序不确定的 [参见 chapter4#Precedence & Associative & Evaluation Order]
形参的名字只是起一个记录作用,在函数的声明中是可以省略的;
函数的形参列表 (parameters list) 可以为空,也可以像 C 语言一样显式的用
void
声明形参为空1
2void f1(/*null*/)
void f2(void)熟悉 C 的程序员常常使用指针作为形参访问函数外面的对象;C++ 中,建议使用引用类型代替指针 (事实上引用也是通过指针实现的);不支持拷贝的对象只能通过引用访问;可以通过引用实现从函数返回值;
形参尽量使用常量引用,比如
iostream
;如果函数不打算修改该参数的话,建议在函数声明的时候对形参加上 const 声明为常量引用;和其他初始化一样,当用实参初始化的时候会忽略顶层 const;换句话说,形参的顶层 const 被忽略掉了,当形参有顶层 const 的时候,传递给他常量变量都可以;正因为如此,以下两个函数的参数列表可以认为是一样的,编译器无法分辨我们希望传入的函数的参数是 const 还是非 const:
1
2
3
4
5
6
7void fcn(const int i);
void fcn(int i); // erorr, redefination of func fcn
// 一个普通的const reference必须使用同类型的对象初始化;
void fcn1(int& i);
const int j=0;
//fcn1(j); // error: no matching function for call to 'fcn1'const reference
&const pointer
: 可以使用非常量去初始化一个底层 const,但是反过来不行;同时一个普通的 const reference 必须使用同类型的对象初始化;1
2
3
4
5
6int i = 42;
const int* cp = &i; // valid
const int& r = i; // valid
const int& r2 = 42; // valid
//int *cpp = cp; // invalid
//int *rr = r; // invalidC++11 允许用字面值 (literal type) 初始化
const lvalue reference
,所以当函数的引用参数是const reference
时,可以把字面值当作实参传递。事实上可以使用任何表达式初始化const lvalue reference
,参见 chapter21
2
3
4
5std::string::size_type find_char(const std::string& s, char c);
find_char("Hello World", 'W'); // use literal init a const reference
std::string ss = "Hellow";
find_char(ss+" World", 'W'); // use expression init a const reference数组作为形参 (not reference to array) 的时候,数组自动转换为指针,数组的大小对函数调用没有影响
以下虽然以下几种声明不太一样,但是是等价的,每个函数都有一个
const int*
的形参,当编译器处理print
的调用的时候,只检查传入的参数是不是const int*
,即数组是以指针的形式传递给函数的;1
2
3
4
5void print(const int*);
void print(const int[]);
void print(const int[10]);
void print(const int* const);
void print(const int* volatile s);当数组当作指针传递给实参的时候,函数并不知道数组的实际大小,管理数组形参一般有三个常见方法:
数组末端置为
'\0'
,类似于 C 字符串,函数内判断是否遇到'\0'
借鉴标准库,用传入数组的 begin 和 end 取代直接传入数组
显式传递数组大小,很多老 C 函数常用的办法
如果数组是用引用 (reference to array) 的方式传递给函数的时候,引用形参要绑定到实参上面,也就是绑定到数组上,此时数组的维度就是类型的一部分,此时函数只能接受维度为 10 的数组作为参数:
1
void print(int (&array)[10]);
多维数组作为函数参数
1
2
3void print(int (*matrix)[10], int row_size);
// 等价定义
void print(int matrix[][10], int row_size); // matrix的声明看起来是一个二维数组,其实是指向含有10个int的数组的指针函数类型可作为函数形参;函数类型作为形参的时候,会被转换为指向函数的指针;下面两个函数是同一种类型
1
2
3
4
5int f(int()); // a function as parameter
int f(int (*g)()); // a pointer to function as paramerter
int test_func(){return 4;}
f(test_func); // redefinition of 'f'一个函数一般只声明一次,但是声明多次也是合法的;需要注意,在给定的作用域中,一个形参只能被赋予一次默认参数,而且该形参右侧的所有形参都必须有默认值;
1
2
3
4
5using sz = std::string::size_type;
std::string screen(sz, sz, char=' '); // valid
//std::string screen(sz=24, sz, char); // invalid
std::string screen(sz=24, sz=80, char); // valid可以使用变量来给默认实参赋值,但是不能是局部变量;用做默认实参的名字在函数声明所在的作用域内解析,但是这些名字的求值过程发生在函数调用时;相当于在函数一开始的时候首先就是调用实参初始化表达式;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17int sz_w = 24;
int sz_h = 48;
int main(){
// func declaration
int tmp_h = 36;
// error: default argument references local variable 'tmp_h' of enclosing function
// std::string screen(sz, sz=tmp_h, char=' ');
// valid, use global variable as default parameter
std::string screen(sz, sz, char=' ');
std::string screen(sz=24, sz=sz_h, char);
// modify sz_h
sz_h = 100;
screen(); // this will call screen(24, 100, '')
}省略符形参 (
...
) 是为了方便某些 C++ 程序更方便访问 C 代码而设置的,这些代码使用了名为varargs
的 C 标准库功能;省略符形参应该仅仅使用于 C 和 C++ 通用的类型的形参,大多数类类型的对象在传递给省略符形参的时候都无法正确拷贝;1
2
3
4
5
6
7
8
9
10
11
12int add_nums(int count, ...) {
int result = 0;
std::va_list args;
va_start(args, count);
for (int i = 0; i < count; ++i) {
result += va_arg(args, int);
}
va_end(args);
return result;
}
std::cout << add_nums(4, 25, 25, 50, 50) << '\n';Alteratives
Variadic templates can also be used to create functions that take variable number of arguments. They are often the better choice because they do not impose restrictions on the types of the arguments, do not perform integral and floating-point promotions, and are type safe.
If all variable arguments share a common type, a std::initializer_list provides a convenient mechanism (albeit with a different syntax) for accessing variable arguments. In this case however the arguments cannot be modified since std::initializer_list can only provide a const pointer to its elements.
可以使用 initializer-list 实现函数接受不同数量但类型相同的参数;
initializer-list
对象中的元素永远是常量值,我们没法改变initializer-list
里面的值;1
2
3
4
5
6
7
8
9void error_message(std::initialzer_list<std::string> il){
for (auto beg=il.begin(); beg<il.end(); beg++){
std::cout << *beg << std::endl;
}
}
error_message({"a"});
error_message({"a", "b"});
error_message({"a", "b", "c"});The common use of
std::initializer_list
is as argument to constructors of container (and similar) classes, allowing convenient initialisation of those containers from a few objects of the same type. Of course, you can usestd::initializer_list
otherwise and then use the same{}
syntax.Since a
std::initializer_list
has a fixed size, it doesn’t require dynamic allocation and hence can be efficiently implemented. Astd::vector
, on the other hand, requires dynamic memory allocation. Even in your simple example it is unlikely that the compiler will optimize this overhead away (avoid the intermediarystd::vector
and its dynamic memory allocation). Other than that, there is no difference in the outcome of your programs (though you should take aconst std::vector<int>&
argument to avoid a copy and its associated dynamic memory allocation).
Return Value
大多数类型都可以作为函数的返回值类型,特殊的如 void 表示什么都没有返回;因为数组不能被拷贝,所以函数不可以返回数组类型和函数类型,但是可以返回指向数组或者函数的指针;
值是如何被返回的?返回一个值的方式和初始化一个变量或者形参的方式完全一样:返回的值用于初始化调用点的一个临时量,该临时量就是函数调用的结果;
不要返回局部对象的引用和指针,函数调用完了局部对象就销毁了,此时他们的引用和指针都是非法的 (dangling reference/pointer);
函数的返回类型决定函数调用是否为左值:如果返回的是一个引用,则返回值是左值;其他的返回类型,返回值是右值;利用这个结果,我们甚至可以为返回非常量引用的函数赋值;
1
2
3
4
5
6
7char& get_char(std::string& str, int index){
return str[index];
}
std::string ss("Hello");
get_char(ss, 0) = h;
std::cout << ss << std::endl; // hello, first char changed from H to hC++11 规定可以使用
initializer-list
作为函数的返回值,用于对返回的临时量的初始化;如果返回值类型是内置类型,花括号内最多包含一个值,而且该值所占空间不应大于返回类型所占空间;如果返回的是类类型,由类本身定义初始值怎么使用;1
2
3
4
5
6int func(){
return {1};
}
std::vector<std::string> func1(){
return {"a", "b", "c"};
}因为数组不能直接拷贝,所以函数返回值不能直接返回数组,不过可以返回数组的指针或者引用;可以用类型别名简化返回类型:
1
2
3
4
5
6
7
8
9typedef int arrT[10]; // arrT是一个类型别名,他的类型是含有10个整形的数组
using arrT1 = int[10]; // 等价于arrT
// function declaration
arrT* func(int i); // func返回一个指向10个整形元素的数组的指针
typedef std::string str_array[10];
using str_array = std::string[10];
str_array& func(); // func返回一个包含10个string的数组的引用理解没有做简化的返回指向数组的指针或者引用的写法
Type ( *function ( parameter-list ) ) [dimension];
Type ( &function ( parameter-list ) ) [dimension];
1
2
3
4
5
6
7
8
9
10// func(int i)定义了func调用的时候需要的参数列表,(*func(int i))意味着我们可以对函数的结果解引用,(*func(int i))[10]表示解引用func的调用将得到一个大小是10的数组,左侧的int表示返回的数组的元素是int型
int arr[10]; // an array of 10 int
int (*arr_ptr)[10]; // pointer to an array of 10 int
int (*func(int i))[10]{ // define a function that returns a pointer to an array of 10 integers.
return arr_ptr;
}
int (*p)[10] = function();
// 返回包含10个string的数组的引用
std::string (&func())[10];C++11 定义了尾置返回类型 (trailing return-type),也可以用来简化数组的返回:
1
2
3
4
5// func 接受一个int型的实参,返回一个指针,该指针指向一个包含10个元素整形的数组
auto func(int i) -> int(*)[10];
// func 返回一个引用,该引用指向一个包含10个string的数组
auto str_array() -> std::string (&)[10];如果已知函数返回的指针或者引用指向某个值,还可以使用
decltype
简化数组的返回:1
2
3
4
5
6
7
8
9
10int odd[] = {1,3,5,7,9}; // array size will be auto detected
int even[] = {2,4,6,8,10};
// decltype 用于数组返回的是一个带有维度的数组类型,如果需要表示arrPtr返回指针,还必须在函数名前面加上一个*
decltype(odd) *arrPtr(int i){
return (i%2) ? &odd : &even; // return a pointer pointed to array
}
std::string str_array[10];
decltype(str_array) &arrFunc(); // return a reference to a array
Overloaded & Matching
对于重载的函数来说,他们应该在形参的类型和数量上有所不同,不允许两个函数除了返回值类型之外其他所有的要素都相同,例如
1
2Record lookup(const Account&);
bool lookup(const Account&); // not an overloaded function of above function函数形参类型的确定规则:
- First, decl-specifier-seq and the declarator are combined as in any declaration to determine the type.
- If the type is “array of T“ or “array of unknown bound of T“, it is replaced by the type “pointer to T“
- If the type is a function type F, it is replaced by the type “pointer to F“
- Top-level cv-qualifiers are dropped from the parameter type (This adjustment only affects the function type, but doesn’t modify the property of the parameter)
一个拥有顶层 const 的形参无法和不拥有顶层 const 的形参区分开来,所以这两种参数类型等价;但如果形参是某种类型的指针或者引用,通过区分其指向的是 const 或非 const 可以实现函数重载,此时的 const 是底层的:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33// 顶层const不影响传入函数的参数
// 这里等价的原因在于:const实参可以既初始化非const形参,也初始化const形参;非const实参也可以既初始化非const形参,也初始化const形参;所以无法通过实参本身是不是const来选择调用哪个函数
Record lookup(Account); // 新函数
Record lookup(const Account); // 不是新函数,与第一条等价
// 底层const会影响传入函数的参数
// 这里可以重载的原因在于:不能把一个const的实参绑定到非const的形参上,形参const reference只能绑定到const实参, 形参pointer to const也只能指向const实参,所以如果实参本身是const,则只能选择调用形参为const的方法;但是非const可以绑定到const reference或者指向pointer to a const,所以当实参为非const的时候,下面的四个函数都可以调用,编译器会优先选择形参为非常量的函数版本,因为非const形参和非const实参本来类型就是完美匹配的,而非const转成const需要额外的类型转换;
// 理解const和非const的绑定:
// - const: read only
// - 非const: read & write
// 所以不能用非const绑const,因为const没有write权限;
// 反过来可以用const绑非const,只不过是reference隐藏了write权限罢了
// pointer 同理
Record lookup(Account&); // 新函数
Record lookup(const Account&); // 新函数
Record lookup(Account*); // 新函数
Record lookup(const Account*); // 新函数
// test
int func(int& b){
std::cout << "c: " << b << std::endl;
return b;
}
int main(){
int a = 10;
func(a); // call func, ok
// clang error: no matching function for call to 'func;note: candidate function not viable: 1st argument ('const int') would lose const qualifier;note: candidate function not viable: no known conversion from 'const int' to 'int *' for 1st argument
const int b = 1;
func(b);
}函数重载与作用域:如果我们在内层作用域中声明相同名称的函数,他将隐藏外层作用域中声明的同名实体,在不同作用域中无法重载函数名;
1
2
3
4
5
6
7
8
9void print(std::string s);
void print(double d);
void fooBar(int ival){
void print(int i); // the inner func declaration overwrite overloaded function in the outer scope
//print("Value"); // error, can not find matched function
print(ival); // valid
print(3.14); // valid, implicit conversion
}函数匹配的过程
- 确定候选函数:与被调用同名且在当前作用域可见的函数集合
- 选出可行函数:根据调用提供的实参,选出和调用函数参数数量相同、类型相同或者可以转换的函数
- 寻找最佳匹配;如果没有最佳匹配,则通过一定的顺序去找最能契合的函数;如果同一优先级有多个函数匹配,则为二义性调用
实参 ——> 形参的匹配优先级:
精确匹配
a) 形参实参类型完全相同
b) 实参从数组类型或者函数类型转换成对应的指针类型;
c) 实参添加顶层 const 或者删除顶层 const;底层 const 转换实现的匹配
1
only non-const -> low leval const
类型提升 (integral promotion, float promotion)
1
2
3
4char -> int;
short -> int;
bool -> int;
float -> double;算术类型转换或者指针类型转换,如,所有算术类型转换的级别都一样
1
2
3
4
5int -> unsigned int;
float -> double;
nullptr -> int*;
int* -> void*;
int* -> const void*;类类型转换实现的匹配 (编译器每次只能执行一次)
1
2const char[] -> std::string;
std::ostream -> bool;
隐式对象参数 (implicit object parameter)
如果类的成员函数 (static or non-static, 除了构造函数) 也是候选函数的时候,将会被视为它有一个隐式的参数,这个参数可以表示这个类自己;这个参数的位置位于第一个真实参数之前;调用的时候,也会假装这个对象是他被调用的成员函数的第一个参数;对于静态成员函数,这个隐式参数可以被任何对象匹配;
C++23 起,可以显式的声明这个对象参数 (explicit object parameter) 了
除了在函数调用的时候会发生函数匹配,以下这些地方也需要函数匹配
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43int f(int) { return 1; }
int f(double) { return 2; }
void g( int(&f1)(int), int(*f2)(double) ) {}
template< int(*F)(int) >
struct Templ {};
struct Foo {
int mf(int) { return 3; }
int mf(double) { return 4; }
};
struct Emp {
void operator<<(int (*)(double)) {}
};
// 1. initialization
int (*pf)(double) = f; // selects int f(double)
int (&rf)(int) = f; // selects int f(int)
int (Foo::*mpf)(int) = &Foo::mf; // selects int mf(int)
// 2. assignment
pf = nullptr;
pf = &f; // selects int f(double)
// 3. function argument
g(f, f); // selects int f(int) for the 1st argument
// and int f(double) for the second
// 4. user-defined operator
Emp{} << f; //selects int f(double)
// 5. return value
auto foo = []() -> int (*)(int) {
return f; // selects int f(int)
};
// 6. cast
auto p = static_cast<int(*)(int)>(f); // selects int f(int)
// 7. template argument
Templ<f> t; // selects int f(int)
Features for Special Usage
内联 (inline) 说明只是向编译器发出的一个请求,编译器可以选择忽略;一般来说内联机制用于优化规模较小、流程直接、调用频繁的函数;心须将 inline 函数的定义都放在头文件中,否则编译时无法进行置换
inline 函数的原理:
当我们定义了一个函数之后,编译器会将其编译成一个指令集合。这个指令集合在程序运行的时候会出现在内存的代码区里,并且在调用此函数时程序执行的地址会跳转到这个指令集合的入口地址,当指令集合执行完后,再跳回到主调函数。换句话说,任何时候内存中只有一个指令集,如果该函数被调用 10 次,则运行时就会跳转到同一入口地址 10 次。
如果定义为 inline 函数,编译器并不创建真实函数,内联函数不仅同普通函数一样经过检查后保存函数名称、参数类型和返回值类型,还会把内联函数的本体也一并存入符号表中,在之后的编译过程中一旦遇到该函数被调用时会首先检查调用是否合法,然后编译器会将 inline 函数的指令集合 (函数代码) 复制嵌入到主调函数中的调用位置,内联函数的代码就会直接替换函数调用,这样就不需要函数调用的跳转开销了。如果函数被调用了 10 次,就相当于内存中就包含 10 个相同指令集合的拷贝,没有一次调用。
了解了内联函数是怎么工作的,那么内联机制的优劣就好理解了。需要清楚的是,我们定义为 inline 函数只是建议编译器进行内联,而不是命令编译器进行内联,所以最后是不是内联函数取决于编译器。还有关键字 inline 必须与函数定义放在一起才能使函数成为内联 (最后由编译器决定),仅放在函数声明前面不起作用。因为 inline 是在编译时展开,必须有实体,在编译阶段,编译器看到 inline 标志就会根据该函数体情况去判断是否应该将该函数体定义为内联。
inline 函数的优缺点:
Pros
- inline 定义的类的内联函数,函数的代码被放入符号表中,在使用时直接进行替换,(像宏一样展开),没有了调用的开销,效率也很高。
- 很明显,类的内联函数也是一个真正的函数,编译器在调用一个内联函数时,会首先检查它的参数的类型,保证调用正确。然后进行一系列的相关检查,就像对待任何一个真正的函数一样。这样就消除了它的隐患和局限性,相对于宏替换不会检查参数类型,安全隐患较大
- inline 函数可以作为一个类的成员函数,与类的普通成员函数作用相同,可以访问一个类的私有成员和保护成员。内联函数可以用于替代一般的宏定义,最重要的应用在于类的存取函数的定义上面。
Cons:
- 内联函数具有一定的局限性,内联函数的函数体一般来说不能太大,如果内联函数的函数体过大,一般的编译器会放弃内联方式,而采用普通的方式调用函数。(换句话说就是,你使用内联函数,只不过是向编译器提出一个申请,编译器可以拒绝你的申请)这样,内联函数就和普通函数执行效率一样了。
- inline 说明对编译器来说只是一种建议,编译器可以选择忽略这个建议。比如,你将一个长达 1000 多行的函数指定为 inline,编译器就会忽略这个 inline,将这个函数还原成普通函数,因此并不是说把一个函数定义为 inline 函数就一定会被编译器识别为内联函数,具体取决于编译器的实现和函数体的大小。
- 因为本质是内联函数是代码块的替换,于是内联函数会增大可执行文件的体积
reference: https://www.cnblogs.com/chenwx-jay/p/inline_cpp.html
constexpr 函数是指可以应用于常量表达式 (constexpr) 的函数,要求:返回值类型和所有形参的类型都得是 literal type,而且函数中有且仅有一个 return 语句;为了能在编译的过程中随时展开,constexpr 函数被隐式地指定为 inline 函数;
constexpr 函数的函数体内可以包含其他语句;这些语句不执行任何操作就行,比如空语句、类型别名及 using 声明
我们允许 constexpr 返回的不是一个常量表达式;此时返回值不一定是 constexpr,即 constexpr 函数不一定返回 constexpr
1
2
3
4
5
6
7constexpr int new_size{ return 42;}
constexpr size_t scale(size_t cnt){ return new_size() * cnt;}
//如果cnt是一个常量表达式,则scale(cnt)也是常量表达式
int arr[scale(2)]; // scale(2) is constexpr
int i = 1;
//int arr2[scale(i)]; // scale(i) is not constexpr通常要把 inline 函数和 constexpr 函数定义在头文件中,方便编译器对这些函数进行展开;和其他函数不同,inline 函数和 constexpr 函数可以在程序中多次定义;不过对于某个给定的函数,它的多个定义必须一致;因此即使在头文件定义函数体也不会出现 duplicatate definiation 的问题
assert 是一种预处理宏 (preprocessor macro),在运行中,如果参数表达式为假,则输出报错信息,如果为真,则什么都不做;他的行为依赖于一个预处理变量 NDEBUG;如果定义了
NDEBUG
,则 assert 什么也不做;默认没有定义这个变量,所有 assert 将会执行运行时检查;1
2
assert(expression); // assure expression is trueassert
&static_assert
static_assert
is good for testing logic in your code at compilation time. the conditions are constexpr or something can be known at compile time1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19// prototype
static_assert( bool-constexpr , message );
// message has to be a string literal, it cannot contain dynamic information or even a constant expression that is not a string literal itself
// check equal
static_assert(03301 == 1729); // since C++17 the message string is optional
// check sizeof long
static_assert(sizeof(long) == 8);
// check template constexpr argument
template <class T, int Size>
class Vector {
// Compile time assertion to check if the size of the vector is greater than
// 3 or not. If any vector is declared whose size is less than 4, the assertion will fail
static_assert(Size > 3, "Vector size is too small!");
};
Vector<int, 4> four; // This will work
Vector<short, 2> two; // This will failassert
is good for checking a case during run-time that you expect should always have one result, but perhaps could somehow produce an unexpected result under unanticipated circumstances.1
2
3
4
5
6
7
8
9// assert is a function-like macro, commas anywhere in condition that are not protected by
// parentheses are interpreted as macro asrgument separators, which is invalid
assert(c == std::complex<double>{0, 0}); // error
assert((c == std::complex<double>{0, 0})); // OK
// There is no standardized interface to add an additional message to assert errors.
// A portable way to include one is to use a comma operator provided it has not been overloaded:
int* p = &i;
assert(("There are five lights", p != nullptr));我们可以在
main.cpp
一开始使用#define
来定义NDEBUG
预处理变量,从而关闭调试状态,也可以通过命令行选项来开启:1
2
3
// or
CC -D NDEBUG main.cpp -o main也可以单独使用
NDEBUG
来控制调试输出1
2
3
4
5
6// __FILE__、__LINE__、__TIME__、__DATE__
void print(cinst int ia[], size_t size){
std::cerr << __func__ << "array size is: " << size << std::endl;
}
Function Pointer
函数指针指向的是函数而不是对象,函数指针由函数的返回类型和形参类型列表共同决定,与函数名无关;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17bool length_compare(const std::string &, const std::string &);
// 上面声明的函数类型为:
bool(const std::string &, const std::string &)
// 指向该函数的指针为
bool (*func_pointer)(const std::string &, const std::string &);
// 自动转换为pointer,和数组类似
func_pointer = length_compare;
// 等价于取地址
func_pointer = &length_compare;
// 可直接调用函数指针,无需解引用
func_pointer("Pang", "Wong");
// 等价于解引用
(*func_pointer)("Pang", "Wong");和数组类似 (数组会自动转换为指向数组的指针),函数类型
不能作为形参(会自动转换为函数指针),但是可以定义函数指针的形参;我们可以直接把函数名当作实参使用,此时他会自动转化为指针;1
2
3
4
5
6
7
8// declaration
void useBigger(const std::string &s1, const std::string &s2,
bool pf(const std::string &, const std::string &));
// equal to declaration
void useBigger(const std::string &s1, const std::string &s2,
bool (*pf)(const std::string &, const std::string &));
// call
useBigger(s1, s2, length_compare);类型别名和 decltype 可以简化函数指针写法;和数组类似,decltype 返回的类型不能直接转换为指针,所以需要加
*
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21// Func1 和 Func2是函数类型
typedef bool Func1(const std::string &, const std::string &);
typedef decltype(length_compare) Func2;
// Func1 和 Func2是函数指针类型(function pointer)
typedef bool (*FuncP1)(const std::string &, const std::string &);
typedef decltype(length_compare) *FuncP2;
//
int fun(int a){
return a;
};
decltype(fun); // int (int)
decltype(&fun); // int (*)(int)
decltype(fun)*; // int (*)(int)
// 等价函数声明
void useBigger(const std::string &s1, const std::string &s2, Func1);
void useBigger(const std::string &s1, const std::string &s2, Func2);
void useBigger(const std::string &s1, const std::string &s2, FuncP2);
void useBigger(const std::string &s1, const std::string &s2, FuncP1);返回指向函数的指针,也可以用尾置返回值 (trailing-return-type) 声明;尾置返回声明在以下几种情况比较适合:
返回值类型依赖模版的参数类型
1
2template<class T, class U>
auto add(T t, U u) -> decltype(t + u);显式的为 lambda 表达式制定返回值
1
2[](int* p) -> int& { return *p; } // OK
int& [](int* p) { return *p; } // ill-formed返回值是类的类型成员,如果在外面写很冗长的时候,可以使用尾置返回
1
2
3
4
5
6
7
8
9
10
11
12
13
14class AReallyLongClassName {
class iterator { /* ... */ };
iterator begin();
};
// OK but verbose
AReallyLongClassName::iterator AReallyLongClassName::begin() {
// ...
}
// OK, and much less verbose
auto AReallyLongClassName::begin() -> iterator {
// ...
}返回值很复杂,如返回指向函数的指针
1
2
3
4
5
6
7
8
9
10
11using F = int(int*, int*);
using PF = int(*)(int*, int*);
PF f1(int); // f1返回指向函数的指针
// 返回类型不能自动转化为指针,我们必须显示的将返回值定位为指针
F f2(int); // invalid
F* f3(int) // f3返回指向函数的指针
// trailing return type
auto f4(int) -> int(*)(int*, int*);
Misc
局部静态变量:程序执行路径第一次经过对象定义的语句时初始化,直到程序终止才被销毁;可以使用变量来初始化局部静态变量;
1
2
3
4
5
6
7
8
9int get_value(){
return 100;
}
for (int i=0; i<100; i++){
static int v = get_value();
v++;
std::cout << v << std::endl; // print start from 100 to 200
}