局部对象
-
在函数块作用域内,内置类型未初始化将产生未定义的值;
-
在函数块作用域内,局部静态对象的定义语句只执行一次初始化,该对象所在函数执行完毕后依然存在,直到程序终止时才会被销毁;
-
在函数块作用域内,如果局部静态变量没有显式初始值,将执行值初始化,内置类型的局部静态变量初始化为0。
#include <cstddef> #include <iostream> // 内置类型未初始化——产生未定义的值 void builtin() { int i, j; std::cout << "i:" << i << std::endl; std::cout << "j:" << j << std::endl; double m, n; std::cout << "m:" << m << std::endl; std::cout << "n:" << n << std::endl; int arr1[2], arr2[3]; std::cout << "arr1:" << *arr1 << std::endl; std::cout << "arr2:" << *arr2 << std::endl; // 标准库类型——具有默认值 std::string s; // 默认初始值"" std::cout << "s:" << (s == "") << std::endl << std::endl; ; } // 局部静态对象——定义语句只执行一次初始化,直到程序终止时才会销毁,所在函数执行完毕后依然存在 size_t f() { static size_t c = 0; return ++c; } void static_var() { std::cout << "c:"; for (size_t i = 0; i < 10; i++) { size_t c = f(); std::cout << " " << c; } std::cout << std::endl << std::endl; } // 内置类型的局部静态变量——将初始化为0 void static_builtin() { static int si, sj; std::cout << "si:" << si << std::endl; std::cout << "sj:" << sj << std::endl; static double sm, sn; std::cout << "sm:" << sm << std::endl; std::cout << "sn:" << sn << std::endl; static int sarr1[2], sarr2[3]; std::cout << "sarr1:" << *sarr1 << std::endl; std::cout << "sarr2:" << *sarr2 << std::endl; } int main() { std::cout << "内置类型局部变量未初始化:" << std::endl; builtin(); std::cout << "静态局部变量仅初始化一次:" << std::endl; static_var(); std::cout << "静态内置类型局部变量初始化为0:" << std::endl; static_builtin(); return 0; } // 内置类型局部变量未初始化: // i:-521777600 // j:32767 // m:6.95325e-310 // n:6.95325e-310 // arr1:249146080 // arr2:32766 // s:1 // 静态局部变量仅初始化一次: // c: 1 2 3 4 5 6 7 8 9 10 // 静态内置类型局部变量初始化为0: // si:0 // sj:0 // sm:0 // sn:0 // sarr1:0 // sarr2:0
函数形参
值参数
- 形参按值传递,是对实参值的拷贝;
- 指针形参也一样,拷贝的是指针的值。形参和实参是指向同一个对象的两个不同指针。
#include <iostream>
#include <string>
// i和s都是值拷贝,改变其值后不影响传入的实参值;
// p是指向实参i的指针,指针被拷贝,可以通过它改变i的值,实参p的值未被改变
void func(int i, std::string s, int *p) {
*p = 100;
p = 0;
i = 2;
s = "world";
}
int main(int argc, const char **argv) {
int i = 0, *p = &i;
std::string s = "hello";
func(i, s, p);
std::cout << "i:" << i << std::endl;
std::cout << "s:" << s << std::endl;
std::cout << "p:" << p << " *p:" << *p << std::endl;
return 0;
}
引用参数
-
引用形参的本质是对传入的实参起了一个别名;
-
当作为参数传递的对象拷贝比较低效或者不支持拷贝时,应当使用引用形参。
-
形参应该尽量使用常量引用。
这样实参的类型具有更多的可选择性,可以是常量,也可以是非常量;同时保护实参的值不被意外改变。
#include <iostream>
#include <string>
void func(const int &i, std::string &s) {
// i = 2; // 不能改变i引用的对象值
s = "world";
}
int main(int argc, const char **argv) {
int i = 0;
std::string s = "hello";
func(i, s);
std::cout << "i:" << i << std::endl;
std::cout << "s:" << s << std::endl;
return 0;
}
// i:0
// s:world
数组参数
- 数组不能拷贝,会被转换成首元素指针,实际执行的是指针拷贝;
- 当不需要对数组元素执行写操作时,应当使用指向const的指针。
#include <iostream>
#include <iterator>
void print(const int *arr, size_t n) {
while (n > 0) {
std::cout << *arr++;
--n;
}
std::cout << std::endl;
std::cout << "----" << std::endl;
}
// 数组参数形式1
void func1(const int *arr, size_t n) { print(arr, n); }
// 数组参数形式2
void func2(const int arr[], size_t n) { print(arr, n); }
// 数组参数形式3
void func3(const int arr[10], size_t n) { print(arr, n); }
// 数组引用参数
void func4(const int (&arr)[3]) {
for (auto elem : arr) {
std::cout << elem;
}
std::cout << std::endl;
std::cout << "----" << std::endl;
}
int main(int argc, const char **argv) {
int arr[] = {1, 2, 3};
size_t n = std::end(arr) - std::begin(arr);
func1(arr, n);
func2(arr, n);
func3(arr, n);
func4(arr);
return 0;
}
// 123
// ----
// 123
// ----
// 123
// ----
// 123
// ----
命令行参数
argc是命令行参数的个数+1,其中+1为程序名字argv是命令行参数列表,类型为const char**, 可以理解为字符串数组;argv第一个元素指向程序的名称,最后一个元素指向最后一个命令行参数,之后的一位元素值保证为0;
#include <iostream>
int main(int argc, const char **argv) {
std::cout << "argc = " << argc << std::endl;
while (*argv) {
std::cout << *argv++ << std::endl;
}
return 0;
}
// 命令行参数 "-f xxx.cc -o xxx.exe", 输出:
// argc = 5
// ./a.out
// -f
// xxx.cc
// -o
// xxx.exe
可变参数
- 如果实参数量未知,但是类型相同,可以使用
initializer_list类型的参数; initializer_list列表中的元素是初始值的副本,且均为const。...省略符形参:只能放在形参列表最后,无须类型检查;- 大多数类型的对象在传递给省略符形参时都无法正确拷贝,能不用就不用。
#include <initializer_list>
#include <iostream>
#include <string>
void init_lst(std::initializer_list<std::string> strs) {
std::cout << "共" << strs.size() << "个参数: " << std::endl;
for (auto s : strs) {
std::cout << s << std::endl;
}
}
void ellipsis(int a, ...) {}
int main(int argc, const char **argv) {
init_lst({"a", "b", "c"});
ellipsis(1, 2, 3);
return 0;
}
返回类型
-
不能返回局部对象的引用或指针, 因为函数执行完局部对象即销毁。
#include <iostream> #include <string> // ok, 返回局部对象副本 std::string ret_localvar() { std::string str = "hello world"; return str; } // not ok, 不能返回局部对象的引用 std::string &ret_ref() { std::string str = "hello world"; return str; } // not ok, 不能返回局部对象的指针 std::string *ret_pointer() { std::string str = "hello world"; return &str; } int main(int argc, const char **argv) { std::cout << ret_localvar() << std::endl; return 0; } -
列表初始化返回值: 对函数返回的临时量进行初始化
#include <iostream> #include <string> #include <vector> std::vector<std::string> ret_initlist() { std::string str; if (str.empty()) { return {"empty"}; } return {str, "others"}; } int main(int argc, const char **argv) { auto strvec = ret_initlist(); for (auto str : strvec) { std::cout << str << std::endl; } return 0; } -
返回数组指针: 四种方式
#include <iostream> int arr[10] = {1, 2, 3}; // 1.直接声明方式 int (*ret_arrptr1())[10] { // ... return &arr; } // 2.类型别名方式 typedef int iarr[10]; iarr *ret_arrptr2() { //... return &arr; } // 3.尾置返回类型的方式 auto ret_arrptr3() -> int (*)[10] { //... return &arr; } // 4.使用decltype的方式 decltype(arr) *ret_arrptr4() { //... return &arr; } int main(int argc, const char **argv) { std::cout << ret_arrptr1() << std::endl; std::cout << ret_arrptr2() << std::endl; std::cout << ret_arrptr3() << std::endl; std::cout << ret_arrptr4() << std::endl; return 0; }
函数重载
函数名称相同但形参列表不同(参数个数不同、参数类型不同),称之为重载。
非重载情形
如下情形不是重载,形参列表实际是相同的:
// 省略形参名称
void func(const int &i);
void func(const int&);
// 使用类型别名
typedef int Int;
void func(const int&);
void func(const Int&);
// 使用顶层const
void func(int); // 整型参数
void func(const int); // 整型常量参数
void func(int*); // 指向整型的指针参数
void func(int* const); // 指向整型的常量指针参数
// --------------------
// 对比: 底层const可以实现重载
void func(int*); // 指向整型的指针参数
void func(const int*); // 指向整型常量的指针参数
void func(int&); // 整型引用的参数
void func(const int&); // 整型常量引用的参数
const_cast与重载
#include <string>
using std::string;
const string &func(const string &s1, const string &s2) {
// ...
return s1.size() < s2.size() ? s1 : s2;
}
// 使用const_cast复用逻辑
string &func(string &s1, string &s2) {
auto &s =
func(const_cast<const string &>(s1), const_cast<const string &>(s2));
return const_cast<string &>(s);
}
int main(int argc, const char** argv) {
return 0;
}
内联函数
作用:在每个调用点上展开,避免函数调用的开销;
应用:常用于规模较小、流程直接、频繁调用的函数;
#include <iostream>
#include <string>
using std::string;
inline const string &func(const string &s1, const string &s2) {
// ...
return s1.size() < s2.size() ? s1 : s2;
}
int main(int argc, const char **argv) {
string s1("hello"), s2("world");
std::cout << func(s1, s2) << std::endl; // 编译时会展开为:
// std::cout << (s1.size() < s2.size() ? s1 : s2) << std::endl;
return 0;
}
constexpr函数
- 常量表达式函数的返回类型及所有形参类型都得是字面值类型;
- 否则,常量表达式函数不一定返回常量表达式;
- 常量表达式函数被隐式地指定为内联函数;
#include <cstddef>
constexpr std::size_t scale(std::size_t sz) { return 2 * sz; }
int main(int argc, const char **argv) {
int arr1[scale(1)] = {1, 2}; // 实参是常量表达式,scale(1) 返回的是常量表达式
int i = 1; // 正确用法: const int i=1; constexpr int i =1; 均可
int arr2[scale(i)] = {1, 2}; // 实参不是常量表达式,scale(i)返回的不是常量表达式
return 0;
}
函数指针
-
当把函数名作为一个值使用时,函数自动转换为指针;
-
不能定义函数类型的形参,函数类型的形参会被自动转换为指向函数的指针;
#include <iostream> #include <string> using std::string; // 函数length_compare bool length_compare(const string &s1, const string &s2) { // ... return s1.length() > s2.length(); } // 声明函数指针 bool (*fptr)(const string &, const string &); // 函数类型作为形参-自动转换为函数指针 string::size_type max_length(const string &str1, const string &str2, bool f(const string &, const string &)) { bool b = f(str1, str2); if (b) { return str1.length(); } return str2.length(); } // 等价的声明-形参显式地声明为函数指针 string::size_type max_length(const string &str1, const string &str2, bool (*f)(const string &, const string &)); int main(int argc, const char **argv) { // 等价初始化 fptr = length_compare; // 自动转换为指针 fptr = &length_compare; // 取址符是可选的 // 等价调用 bool b1 = fptr("hello", "world"); // 自动转为所指的函数,再调用 bool b2 = (*fptr)("hello", "world"); // 解引用,再调用 bool b3 = length_compare("hello", "world"); // 直接调用 std::cout << b1 << " " << b2 << " " << b3 << std::endl; string::size_type max_len = max_length("hello", "world", fptr); std::cout << max_len << std::endl; return 0; } -
不能返回函数,但可以返回一个指向函数的指针。和函数类型的形参不同,返回函数类型不会自动地转换成指针;
#include <iostream> #include <string> using std::string; // 函数length_compare bool length_compare(const string &s1, const string &s2) { // ... return s1.length() > s2.length(); } // 返回函数指针 // 1. 直接声明 bool (*f(const string &, const string &))(int); // 2. 类型别名 typedef bool (*Func)(const string &, const string &); Func f(int); // 3. 尾置类型 auto f(int) -> bool (*)(const string &, const string &); // 4. 使用decltype decltype(length_compare) *f(int); int main(int argc, const char **argv) { return 0; }
参考书籍:《C++ Primer》第5版