1. 参数传递
1.1 含有可变形参的函数
1. initializer_list
initializer_list 是一种标准库类型,用于表示某种特定类型的值的数组。它可以接受多个参数,向量vector初始化等场合会用到。
void print(std::initializer_list<int> vals) {
cout << vals.size() << ": ";
for (auto i = vals.begin(); i != vals.end(); i++)
cout << *i << " ";
cout << endl;
}
int main() {
print({1, 2});
}
第 9 行使用大括号括住多个实参向可变形参传递参数。
2. 省略符形参
3. 可变参数模板
可变参数模板在《模板与泛型编程》章节。
2. 返回类型
大多数类型都能作为函数返回值,比如void、指针和引用。函数的返回值不能是数组类型或函数类型,但可以是它们的指针或引用。
1. 返回数组的指针或引用
返回值可以使用类型别名,也可以不用。返回的是数组指针和引用而不是首元素的指针和引用,因而使用sizeof得到的是数组的大小。
int a[10] = { 0,1,2,3,4,5,6,7,8,9 };
// 1.使用类型别名返回数组的指针和引用。
typedef int arr[10];
// using arr = int[10]; //作用同上一行。
arr* pa1() { return &a; }
arr& ra1() { return a; }
// 2.不用类型别名返回数组的指针和引用。
int(*pa2())[10] { return &a; }
int(&ra2())[10] { return a; }
int main() {
int(*py)[10] = pa1(); //pa2用法相同。
int (&ry)[10] = ra1(); //ra2用法相同。
cout << sizeof(*py)<<" "<< sizeof(ry) << endl; //数组大小。
cout << ry[3] << " " << (*py)[3] << endl; //a[3]的值。
}
2. 返回函数的引用
函数一般只有指针,下面程序中把*换成&效果是一样的。
void fun() { cout << "--function." << endl; }
// 1.使用类型别名返回函数的指针。
typedef void (*Pf)();
//using Pf = void(*)(); //作用同上一行。
Pf pf1() { return fun; }
// 2.不用类型别名返回函数的指针。
void (*pf2())() { return fun; }
int main() {
pf1()();
pf2()();
}
3. 不能返回局部对象的指针或引用
局部非静态对象在离开代码块时析构,返回的指针或引用关联的对象也就不存在了。
struct Example {
int a = 7;
~Example() { a = -7; }
};
Example f1() { Example e; return e; }
Example& f2() { Example e; return e; }
Example* f3() { Example e; return &e; }
int main() {
Example e1 = f1(); cout << e1.a << endl; // 7
Example e2 = f2(); cout << e2.a << endl; // -7
Example& e3 = f2(); cout << e3.a << endl; // -7
Example* e4 = f3(); cout << e4->a << endl; // -7
}
第11、12、13行接收返回的局部变量的指针或引用。指针或引用关联的对象已经析构了,虽然还可以访问到析构前对象的部分成员,但是这些数据已经无效了,只是还没被覆盖。
局部静态对象存储在静态内存,在程序的执行路径第一次经过对象定义语句时初始化,直到程序终止才销毁。所以,可以返回局部静态对象的指针和引用。
4. 函数返回的过程
struct Example {
Example() { cout << "--无参构造函数。" << endl; }
Example(const Example& e) { a = e.a; cout << "--拷贝构造函数。" << endl; }
Example& operator=(const Example& e) { a = e.a; cout << "--拷贝复制函数。" << endl; return *this; }
Example(Example&& e) { a = e.a; cout << "--移动构造函数。" << endl; }
Example& operator=(Example&& e) { a = e.a; cout << "--移动复制函数。" << endl; return *this; }
~Example() { a = -7; cout << "--析构函数。" << endl; }
int a = 7;
};
Example f1() { Example e; return e; }
Example& f2() { Example e; return e; }
Example* f3() { Example e; return &e; }
int main() {
Example e11 = f1(); // 无参构造e、用e(移动构造临时对象、用临时对象)移动构造e11。
Example& e12 = f1(); // 无参构造e、用e移动构造临时对象,用e12引用临时对象。
Example e13; e13 = f1(); // 无参构造e13、无参构造e、用e移动构造临时对象、析构e、用临时对象移动复制给e13、析构临时对象。
Example e21 = f2(); // 无参构造e、析构e、用析构后的e拷贝构造e21。
Example& e22 = f2(); // 无参构造e、析构e、用e22引用析构了的e。
Example* e3 = f3(); // 无参构造e、析构e、用e3指向析构了的e。
}
返回局部对象
当返回局部非静态对象时,用返回值移动构造一个临时对象,再用这个临时对象:移动构造e11、关联e12(临时对象不析构)、移动复制给e13。其中移动构造e11这种情况会被优化成直接使用返回值移动构造e11。
当返回局部静态对象时,用返回值拷贝构造一个临时对象,再用这个临时对象:XX构造e11、关联e12(临时对象不析构)、移动复制给e13。其中XX构造e11这种情况会被优化成直接使用返回值拷贝构造e11。
返回局部对象的指针或引用
当返回局部非静态对象的指针或引用时,先析构局部对象,再用析构后的对象:拷贝构造e21、关联e22、关联e3。
当返回局部静态对象的指针或引用时,直接使用返回值:拷贝构造e21、关联e22、关联e3。
局部对象离开作用域会被析构,被指针或引用关联的临时对象不会被析构。
当返回值类型不是指针也不是引用时,因为返回值是右值,用返回值赋值时优先调用移动函数,没有移动函数会调用拷贝函数,两者都没有会出错。比如智能指针unique_ptr的拷贝函数是删除的,它作为返回值类型时调用的就是移动函数。
3. 几种函数
1. 常量表达式函数
一些地方必须使用常量表达式。比如:当一个模板实例化时,非类型参数被一个用户提供的或编译器推断出的值所代替,这些值必须是常量表达式。
常量、常量表达式、常量表达式函数都可以为一个常量表达式赋值。所以这里介绍一下常量表达式函数。
常量表达式函数的返回类型及所有形参的类型都得是字面值类型,而且函数体中必须有且只有一条return语句。
constexpr int get(int x) { return x + 1; }
int main() {
const int a = 1;
constexpr int b = 1;
constexpr int y = get(a);
}
把非constexpr函数定义成constexpr函数,编译时不报错,运行时会报错。
2. 内存操作函数
memcpy, memmove, memcmp, memchr, memset