c++基础:6-函数

271 阅读3分钟

局部对象

  • 在函数块作用域内,内置类型未初始化将产生未定义的值;

  • 在函数块作用域内,局部静态对象的定义语句只执行一次初始化,该对象所在函数执行完毕后依然存在,直到程序终止时才会被销毁;

  • 在函数块作用域内,如果局部静态变量没有显式初始值,将执行值初始化,内置类型的局部静态变量初始化为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版