C++ Primer: Functions

2020-05-13  本文已影响0人  algebra2k

1. 函数基础

形参和实参
实参是形参的初始值. 形参和实参类型要一致, 顺序要对应. (但编译器具体按什么顺序求值则不确定)

局部对象

形参和函数内部的变量是局部变量, 仅在函数作用域可见.
所有函数体之外的对象存在于整个程序的生命周期, 直到程序退出.

自动对象
对于在函数执行时创建对象, 函数退出时销毁的对象称为 自动对象.
形参也是自动对象, 函数开始时为其申请空间, 函数退出时形参也被销毁.

局部静态对象
使用static声明的局部对象称为 局部静态对象. 这种对象在函数执行时创建, 但函数退出时却不销毁, 直到程序退出时才销毁**, 生命周期贯穿整个程序.

函数声明

函数必须在使用前声明
函数可以有多次声明, 但只能有一次定义. 函数声明也称为 函数原型.

2. 函数参数

参数传递

const形参和实参
形参有顶层const时, 实参传递常量对象或非常量对象都可以

// 变量初始化和形参初始化对比
int i = 0;
const int *cp = &i; // 正确, 但cp不能改变i
const int &r = i; // 正确, 但r不能改变i
const int &r2 = 42; // 正确
int *p = cp; // 错误, p的类型和cp类型不匹配
int &r3 = r; // 错误, r的类型和r3类型不匹配
int &r4 = 42; // 错误, 不能用字面值初始化一个引用

int i = 0;
const int ci = i; 
string::size_type ctr = 0;
reset(&i); // 调用参数是int*的reset
reset(&ci); // 错误, 不能用指向const int的对象初始化int *
reset(i); // 调用参数时int&的reset
reset(ci); // 错误, 不能把普通引用绑定到const上
reset(42); // 错误, 不能把普通引用绑定到字面值上
reset(ctr); // 错误, 类型不匹配, size_t是无符号类型

尽量使用常量引用

数组形参
数组两个特殊的性质

  1. 数组不允许拷贝, 意味着不能通过值传递一个数组实参
  2. 数组会被转化为指针, 意味着传递一个数组实参时, 实际上传递的首元素指针

传递方式

  1. 使用标准库规范
void print(const  int *beg, const int *end)
{
     while(beg != end)
        cout << *beg++ << endl;
}

int j[2] = {0, 1};
print(begin(j), end(j));
  1. 显式传递一个数组大小的实参数
void print(const  int ia[], size_t size)
{
    for (size_t i = 0; i < size; i++)
        cout << ia[i] << endl;
}

int j[2] = {0, 1};
print(j, end(j) - begin(j));

当函数不需要写操作, 数组的形参应该是const类型的指针.

  1. 数组引用形参
void print(int (&arr)[10])
{
    for (auto elem: arr)
        cout << elem << endl;
}

&arr 括号不能少, 因为 f(int &arr[10]) 表示arr是引用的数组, f(int (&arr)[10]) 是具有10个整数的数组引用.

传递多维数组
c++没有真正的多维数组, 多维数组在c++中是数组的数组, 因此和数组一样, 多维数组指向数组的首元素

void print(int (*metrix)[10), int rowSize) { /**...**/};

main函数参数

可变形参函数

initializer_list 是标准库类型, 表示特定类型值的数组

void err_msg(int err_code, initializer_list<string> errors) {
     cout << "code " << err_code;
     for (auto beg = erros.begin(); beg != errors.end(); beg++)
       cout << *beg << " ";
     cout << endl;
}

// initializer_list形参在传递实参时, 需要传递一个列表值
err_msg(500, {"e1", "e2", "e3"})

c++兼容了c的省略符号形参和varargs void f(param_list, ...);

3. 函数返回值

引用返回左值

char &get_val(string &str, string::size_type ix)
{
      return str[ix];
}

int main() 
{
      string a("value");
      cout << a << endl;
      // get_val返回左值引用
      get_val(a, 0) = 'V';
      cout << a << endl;
}

列表初始化返回值

vector<string> process() 
{
      //...
      return {"a", "b"};
}

返回数组指针
由于数组不能拷贝, 因此返回数组返回的是数组指针, 这种返回类型函数的声明方式可以有以下几种

  1. 使用typedef或using
typedef int arrT[10];

using arrT = int[10]; // 和typedef等价
arrT *func(int i); // 返回一个指向10个整数的数组的指针
  1. 不使用类型别名的声明
    语法 Type (*function (parameter_list))[dimension]
    上面的可以声明为 int (*func (int i))[10]

分解声明的每个部分的含义:

  1. 使用尾置返回类型(c++11)
auto func(int i);
  1. 使用decltype (c++11)

** 前提是知道了返回的数组指针指向哪个数组**

int odd[] = {1, 3, 5};
int even[] = {2, 4, 6};
decltype(odd) *arrPtr(int i)
{
  return (i % 2) ? odd : event;
}

4. 函数重载

main函数不能重载

重载函数是名字相同, 但形参类型不同的一组函数集合

Record lookup(const Account&);
Record lookup(const Phone&);
Record lookup(const Name&);

const影响重载的识别吗
顶层const形参不会认为是不同的参数

Record lookup(Phone);
Record lookup(const Phone); // 重复声明

Record lookup(Phone *);
Record lookup(Phone * const); // 重复声明

底层const实现函数重载

Record lookup(Account &);
Record lookup(const Phone&); // 新函数, 常量引用

Record lookup(Account *);
Record lookup(const Account *); // 新函数, 常量指针

const_cast和重载

const string &shorterString(const string &s1, const string &s2)
{
      return s1.size() < s2.size()? s1 : s2;
}

// 重载函数, 非常量引用
string &shorterString(string &s1, string &s2)
{
    // s1和s2都是非常量引用, 通过const_cast转为常量引用, 调用了shorterString重载函数
    auto &r = shorterString(const_cast<const string&>(s1), const_cast<const string&>(s2));
    // 重载函数返回的类型是常量引用, 通过const_cast转为非常量引用返回
    return const_cast<string&>(r);
}

作用域和重载

5. 函数特殊特性

默认实参

应该在函数声明中就声明默认实参, 并放在合适的头文件中

默认实参行为: 调用函数没有传递参数则使用默认实参

默认实参声明

string screen(sz = 24, sz = 80, char);

默认实参声明一个参数只能声明一次

string screen(sz, sz, char = '');
string screen(sz, sz, char = '*'); // 错误, 重复声明

constexpr函数
constexpr变量定义时如果用到了函数, 则函数必须也是constexpr函数

char *s = "hello";
constexpr size_t size = strlen(s) + 1; // 错误, strlen不是constexpr函数

constexpr int new_size() { return 42; }
constexpr int foo = new_size() + 1; // 正确
constexpr size_t scale(size_t cnt) { return new_sz() * cnt };

int arr[scale(2)]; // 正确scale是常量表达式, 传递的cnt实参也属于常量
int i = 2;
int a2[scale(i)]; // 错误, i不是constexpr

内联函数
内联函数声明

inline const string &shorterString(const string &s1, const string &s2)
{
      return s1.size() < s2.size()? s1 : s2;
}

声明内联函数, 编译器会尝试展开函数, 但不包装声明inline的函数100%做成内联的.

内联函数适合优化规模较小、流程直接、频繁调用的函数

调试帮助

  1. assert预处理宏
    assert(expr) 如果expr为真, 输出信息并终止程序执行, 如果为假什么也不做
    assert宏常用于检测不能发生的条件

  2. 预处理宏

6. 函数指针

函数指针声明

// 原型函数
bool lengthCompare(const string &, const string &);
bool (*pf)(const string &, const string &);

使用函数指针

// 原型函数
pf = lengthCompare; 
pf = &lengthCompare; // 取地址符是可选的

重载函数指针
重载函数可以有对应的函数指针, 编译器会决定选择哪个函数

指针作为函数形参

void userBigger(const string &s1, const string &s2,
                          bool pf(const string&, const string &);
void userBigger(const string &s1, const string &s2,
                          bool (*pf)(const string&, const string &); // 等价声明, 函数名会自动转换

返回函数指针

  1. 使用typedef或using
using F = (int *, int); // F是函数类型
using PF = int(*)(int *, int); // PF是函数指针类型
  1. 直接声明
    int(*f1(int))(int *, int);

  2. 使用auto
    auto f1(int);

  3. 使用decltype

string::size_type sumLength(const string&, const string &);
decltype(sumLength) *getFn(const string &); // 返回类型是一个函数指针
上一篇下一篇

猜你喜欢

热点阅读