本 Wiki 开启了 HTTPS。但由于同 IP 的 Blog 也开启了 HTTPS,因此本站必须要支持 SNI 的浏览器才能浏览。为了兼容一部分浏览器,本站保留了 HTTP 作为兼容。如果您的浏览器支持 SNI,请尽量通过 HTTPS 访问本站,谢谢!
这是本文档旧的修订版!
第 6 章笔记
封装了代码,用于反复利用
函数定义出现多次的例外:inline
调用函数时,函数内部的变量(局部变量)是通过 stack frame 的结构进行组织的。当牵涉到多重调用时,越往后调用的函数越处于 stack 的上方。
int Add(int x, int y)
{....}
int main()
{
int x = 1;
int y = 2;
// Add 在 stack 中处于 main() 的上方
// 从下到上,内存地址是逐渐减小的
Add(1,2);
}
以下面的程序为例:
int Add(int x, int y)
{
return x + y;
}
int Sub(int x, int y)
{
return x - y;
}
int main(int argc, char const *argv[])
{
/* code */
return 0;
}
# list all external link of demo
nm demo
# 需要关注的内容
# main
0000000140001476 T main
# add & sub
# 注意是 mangling 的名称
# 编译器友好形式
0000000140001450 T _Z3Addii
0000000140001464 T _Z3Subii
#demangle
nm demo | c++filt -t
# 结果,链接中实际保存了参数的信息
# 阅读友好形式
0000000140001450 T Add(int, int)
0000000140001464 T Sub(int, int)
extern “C”
,请求编译器将该函数声明为 C 友好的外部链接形式0
到多个,参数列表为 0 时可以用 void
代替
// 0参数 等价形式
void fun(){};
void fun(void){};
// 无参数名称的函数
void fun(int, int y) {}
// 名称不影响函数在编译器内的匹配
// redefination
void fun(int, int y) {};
void fuin(int a, int b) {};
// C++ 17 强制省略复制临时对象
struct Str
{
Str() = default;
Str(const Str&) {};
}
void fun(Str) {};
int main()
{
// 拷贝构造
Str a;
fun(a);
// 临时对象不会拷贝构造
fun(Str{});
}
都是拷贝初始化的过程,但是行为不同:
// 注意这里的函数参数
// int (*)[] 指参数类型是指向元素为一维数组的数组指针
void func2(int (*par)[]) { }
// 等价写法
// 注意 [4],元素数组的长度必须与 argument 匹配
void func2(int [][4]) {}
int main()
{
int a[3][4];
func2(a);
return 0;
}
// 注意引用和指针的区别
// 此时不会退化,因此以二维数组的作为实参的形参必须是二维数组的引用
// 该方法可以传递数组长度
void func4(int (&ref)[3][4]) {}
int main()
{
int a[3][4];
func4(a);
// error: invalid initialization of reference of type
int b[2][4];
func4(b);
}
// 自定义缺省参数
void fun(int x = 0) {};
// 调用 fun(0)
fun();
// 声明中可以进行缺省参数默认值的定义
// 但同单元只能定义一次
void fun(int x, int y = 2, int z = 3);
//error: previous specification
void fun(int x, int y = 2, int z = 3)
{
std::cout << x + y + z << std::endl;
}
int main()
{
fun(1);
return 0;
}
// ok
void fun(int x, int y = 2, int z = 3);
void fun(int x = 1, int y, int z);
void fun(int x, int y, int z)
{
std::cout << x + y + z << std::endl;
}
int main(int argc, char const *argv[])
{
fun();
return 0;
}
这种情况主要适用于多个翻译单元同时存在的情况下:编译器会根据不同的翻译单元在编译期将不同的默认参数赋予当前的翻译单元。存在这种机制主要是为了允许我们对默认参数的分级控制。比如存在如下三个文件:
假设 Header 被 source 和 main 都引用,那么:
通常,缺省值不太被改动的参数,都会放到函数的最后面。此类默认缺省值的参数设定一般都至于比较靠近根部的翻译单元中(header)
int x = 3;
void fun(int y = x){}
// 调用 fun(x),而不是fun(3)
fun();
int main(int argc, char const *argv[])
{
for(int i =0; i < argc; ++i)
{
std::cout << argv[i] << "\n";
}
return 0;
}
# args
# 1 为 argc = 0; 存储于 argv[0]
# ...
# 4 存储于 argv[3]
demo 1 2 3 4
argv[0]
一定是指当前程序名,可以用其表示当前程序名
std::cerr << "Usage: " << argv[0] << "detals ....";
void
函数通常是隐式返回main()
也支持隐式返回,返回类型为 int
,正常结束的程序返回值为 0
return
+ 语句 / 表达式 / 初始化列表
// void 的显式返回
// 可以用作跳出当前函数体的手段
// 如果有返回值类型,那么返回值必须匹配该类型
return;
std::vector<int> fun2()
{
return {1,2,3,4,5};
}
# 初始化和返回默认情况下都会进行拷贝构造
# 系统会开辟内存->拷贝到内存->初始化
# 将拷贝的行为替换为直接在申请内存上直接构造,省略拷贝的过程
# 关闭返回值优化
# not work on C++ 17
fno-elide-constructors
// 经典返回
int fun(int a, int b) {}
// 尾部返回 C++11
// 使用泛型编程的时候,参数的类型由模板参数决定,在后面写返回类型更方便
// 返回类的成员函数的自定义类型,在后面写更方便
// 尾部返回可以自动查找域
auto fun(int a, int b) -> int {}
// 自动推导返回 C++14
// 基于 return 语句进行推导
// 返回 int
auto fun(int a, int b) { return a + b; }
如果存在多个 return 语句,那么返回结果的类型必须一致,否则需要使用 constexpr if 来处理:
// 与运行期条件不同,constexpr 在编译器生效
// 通过 constexpr value 来舍弃某个分支的 return
// 实际上编译结果只存在一个 return 的类型
constexpr bool value = true;
auto fun()
{
if constexpr (value)
{
return 1;
}
else
{
return 3.14;
}
}
struct Str
{
int x;
int y;
};
Str fun()
{
return Str{};
}
int main(int argc, char const *argv[])
{
// C++ 17
// v1 v2 实际上是引用
// 使用 v1, v2 直接绑定了 x 和 y
auto [v1, v2] = fun();
v1;
v2;
//Str res = fun();
return 0;
}
// 使用 warning 提醒该返回值非常重要,不能忽略
[[nodiscard]] int fun(int a, int b)
{
return a + b;
}
int main()
{
// 返回值没有用到
// warning: ignoring return value
fun(2, 3);
}
重载函数会被 mangle 为以下的格式:
# 除了函数名,还有参数的信息
000000014000145c T _Z3fund
0000000140001450 T _Z3funi
void fun()
{
std::cout << "global fun\n";
}
namespace myNS
{
void fun()
{
std::cout << "NS fun\n";
}
void g()
{
fun();
}
}
int main(int argc, char const *argv[])
{
// 限定名称查找
// 指定了查找的区域
// 域可以由 Namespace,class(sub class) 形成
::fun();
myNS::fun();
// 非限定名称查找
// 会从当前的域(myNS)开始查找
// 一旦查找到名称,就会停止 name lookup
// 调用 myNS::fun()
myNS::g();
// 如果当前域不存在 fun() 则会去上级查找
// 查找的顺序从上到下,如果域内的 myNS::fun() 的定义在 g() 之后
// 那么会调用 ::fun()
// 可以提前声明 myNS::fun() 来告诉编译器本域中存在 myNS::fun() 的定义
return 0;
}
级别越低,匹配程度越高
// 左值会优先选择非 const 版本
// 编译器会倾向选择保证对应变量的访问权限的函数
void fun(int& x){}
void fun(const int& x){}
// int&
fun(x);
// const int&
fun(3);
main
中执行inline
关键字修饰的函数可以重复定义。inline
函数保证同时只有一个定义用于链接期。这使得函数定义的范围从翻译单元变为了跨翻译单元。inline
的定义应该与其声明处于同一位置