C++ Primer: Functions

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函数参数

  • argc 表示元素数量
  • **argv[] 表示多个c风格字符串的数组, 具体有多少个由argc决定

可变形参函数

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]

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

  • func (int i), 表示函数调用需要一个int类型的实参
  • (func(int i)), 表示对调用函数可以执行解引用*操作
  • (*func (int i))[10], 表示调用func 解引用 得到一个大小是10的数组
  • int (*func (int i))[10], 表示数组的类型是int
  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. 预处理宏

  • __func__, 函数名
  • __FILE__, 文件名
  • __LINE__, 行号
  • __TIME__, 文件编译时间
  • __DATE__, 文件编译日期

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 &); // 返回类型是一个函数指针
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,980评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,178评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,868评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,498评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,492评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,521评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,910评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,569评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,793评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,559评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,639评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,342评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,931评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,904评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,144评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,833评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,350评论 2 342