C++ 笔记:函数

本文深入讲解C++函数的基础知识,包括形参与实参、局部与全局对象、参数传递方式、默认参数、数组形参、initializer_list形参、返回值处理、函数重载及函数指针的概念与使用技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

函数基础

形参和实参

  • 编译器能以任意可行的顺序对实参求值
  • 实参数量应与形参数量一致,所以形参一定会被初始化

局部对象

  • 形参和函数体内部定义的变量统称为局部变量,局部变量仅在函数的作用域内可见,并将外层作用域中的同名变量隐藏
  • 在所有函数体之外定义的对象存在于程序的整个执行过程中。此类对象在程序启动时被创建, 直到程序结束才会销毁
  • 局部静态对象在程序的执行路径第一次经过对象定义语句时初始化,并且直到程序终止才被销毁,在此期间即使对象所在的函数结束执行也不会对它有影响:
size_t countCalls() {    // 计算函数被调用的次数
  static size_t count = 0;
  return ++count;
}
  • 内置类型的局部静态变量如果没有显式的初始值,则初始化为 0

参数传递

传引用参数

  • 使用引用形参避免拷贝:拷贝大的类类型对象或者容器对象比较低效,甚至有的类类型(包括 IO 类型)根本就不支待拷贝操作。当某种类型不支待拷贝操作时, 函数只能通过引用形参访问该类型的对象
  • 当函数无须修改引用形参的值时使用常量引用

const 形参和实参

  • 和其他初始化过程一样,当用实参初始化形参时会忽略掉顶层 const,因此下面两个函数是重复定义:
void foo(const int i) {}
void foo(int i) {}
  • 尽量使用 const 引用作为形参:如果采用普通引用作为形参,则无法将 const 对象、字面值或需要类型转换的对象传递给函数

默认参数

  • 调用含有默认实参的函数时,可以包含该实参,也可以省略该实参
  • 一旦某个形参被赋予了默认值,它后面的所有形参都必须有默认值
  • 函数调用时实参按其位置解析,默认实参负责填补函数调用缺少的尾部实参

数组形参

  • 为函数传递一个数组时,实际上传递的是指向数组首元素的指针
// 以下三个函数等价
void foo(const int *arr);
void foo(const int arr[]);
void foo(const int arr[10]);  // 数字 10 会被忽略,数字大小对函数调用没有影响

int i = 1;
int arr[5] = {0, 1, 2, 3, 4}; 
foo(&i);    // 正确调用
foo(arr);   // 正确调用
  • 因为数组是以指针的形式传递给函数的, 所以一开始函数并不知道数组的确切尺寸,调用者应该为此提供一些额外的信息,以确保指针不会越界
  • 传递指向数组首元素和尾后元素的指针:
void print(const int *begin, const int *end) {
    while (begin != end)
      cout << *begin++ << endl;
}

print(begin(arr), end(arr));   // #include <iterator>

main() 处理命令行选项

int main(int argc, char **argv) {}
  • 当实参传给 main 函数之后, argv 的第一个元素指向程序的名字或者一个空字符串,接下来的元素依次传递命令行提供的实参。最后一个指针之后的元素值保证为 0

initializer_list 形参

  • 如果函数的实参数量未知但是全部实参的类型都相同,可以使用 initializer_list 类型的形参
  • initializer_list 是一种模板类型,用于表示某种特定类型的值的数组,其提供的常用操作有:
initializer_list<T> lst;                // 空列表
initializer_list<T> lst{a, b, c, ...};  // 列表中的元素均为 const
lst2(lst1);                             // 拷贝或者赋值不会复制其中的元素,而是共享元素
lst2 = lst1;
lst.size();
lst.begin();    // 指向首元素的指针
lst.end();      // 指向尾元素下一个位置的指针
  • initializer_list 对象中的元素永远是常量值, 不允许改变 initializer_list 对象中元素的值
  • 使用 initializer_list 可以传递未知数量的同类型参数:
void errorMsg(initializer_list<string> ini_list) {
  for (auto msg : ini_list)
    cout << msg << endl;
}
  • 想向 initializer_list 形参中传递一个值的序列,则必须把序列放在一对花括号内
errorMsg({string1, string2, ...});
  • 含有 initializer_list 形参的函数也可以同时拥有其他形参

返回值

  • 函数返回一个值的方式和初始化一个变量或形参的方式完全一样:返回的值用于初始化调用点的一个临时量, 该临时量就是函数调用的结果
  • 不允许返回局部对象的引用或指针:函数终止意味着局部变量的引用或指针将指向不再有效的内存区域
  • 调用一个返回引用的函数得到左值,其他返回类型得到右值
  • 函数可以返回花括号包围的值的列表

函数重载

  • 重载的函数应该在形参数量或形参类型上有所不同
  • 顶层 const 不影响传入函数的对象,因此一个拥有顶层 const 的形参无法和另一个没有顶层 const 的形参区分开来
void print(string str);
void print(const string str);     // 重复声明了 void print(string str);

void print(double *d_ptr);
void print(double *const d_ptr);  // 重复声明了 void print(double *d_ptr);
  • 底层 const 的区别则可以区分为不同的函数
    • 只能将 const 对象或指向 const 的指针传递给 const 形参
    • 传递一个非常量对象或者指向非常量对象的指针时,编译器会优先选用非常量版本的函数
void print(string &str);
void print(const string &str);    // 可以区别于 void print(string &str);

void print(double *d_ptr);
void print(const double *d_ptr);  // 可以区别于 void print(double *d_ptr);

函数指针

  • 函数的类型由它的返回类型和形参类型共同决定,与函数名无关
  • 要声明一个指向函数的指针,只需要用指针替换函数名即可:
bool lengthCompare(const string &str1, const string &str2); // 该函数的类型为
                                                            // bool(const string &, const string &)

bool (*func_ptr1)(const string &, const string &);          // 声明指向函数的指针 func_ptr1,未初始化
bool (*func_ptr2)(const string &, const string &) = lengthCompare;          
                                                            // 声明指向函数的指针 func_ptr2 并初始化
  • 函数名即为函数的地址;把函数名作为一个值使用时,该函数名自动地转换成指针
  • 可以直接使用指向函数的指针调用该函数,无须提前解引用指针
func_ptr1 = lengthCompare;  // 赋值,等价于 func_ptr1 = &lengthCompare; 取地址符可省略 
// 以下三条函数调用语句等价:                                                        
bool b1 = lengthCompare("hello", "bye");                 
bool b1 = func_ptr1("hello", "bye");                 
bool b1 = (*func_ptr1)("hello", "bye"); // 解引用符可省略
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值