在前面我们已经使用过定义main函数,以及也见过其他的自定义函数,函数是一个命名了的代码块,我们通过调用函数执行相应的代码,函数可以有0个或多个参数,而且通常产生一个结果,C++可以重载函数,也就是说,同一个名字可以对应几个不同的函数
函数基础函数的实参的类型要与函数的形参类型匹配,后者实参赋值给形参可以进行类型转换。
//example1.cpp #include函数参数列表using namespace std; //编写函数 返回类型为int int double_(int num) { return 2 * num; } int main(int argc, char **argv) { //调用函数 cout << double_(3) << endl; //实参为3 //形参为num return 0; }
函数的形参可以为多个,形成函数的形参列表
//example2.cpp #include局部对象using namespace std; int mul(int num1, int num2) { return num1 * num2; } int main(int argc, char **argv) { cout << mul(2, 3) << endl; // 6 return 0; }
在C++中,名字是有作用域的,对象有生命周期,形参和函数内部定义的变量统称为局部变量,其作用域在函数内部,且一旦函数执行完毕,相应内存资源被释放即栈内存。分配的栈内存将会保留,直到我们调用free或者delete。
//example3.cpp #include局部静态组件using namespace std; int &func() { int i = 999; return i; } int *func1() { int *i = new int(999); return i; } int main(int argc, char **argv) { int *num = func1(); cout << *num << endl; // 999 delete num; int &i = func(); cout << i << endl; //程序会报错,为什么,因为func调用完毕后其内的i变量内存被释放,所以相应的引用是无效的 return 0; }
局部静态对象在程序的执行路径第一次经过对象定义语句时进行初始化,并且直到程序终止才被销毁,在此期间即使对象所在的函数结束执行也不会对它有影响。
//example4.cpp #include函数声明using namespace std; int count() { static int num = 0; ++num; return num; } int main(int argc, char **argv) { cout << count() << endl; // 1 cout << count() << endl; // 2 for (int i = 0; i < 4; i++) { cout << count() << endl; // 3 4 5 6 } return 0; }
函数的名字必须在使用前声明,与变量类似,函数只能定义一次,但可以声明多次。
//example5.cpp #include声明提升using namespace std; int main(int argc, char **argv) { // func();// error: 'func' was not declared in this scope //在每调用前没有声明或者定义 return 0; } void func() { cout << "hello function" << endl; }
//example6.cpp #include在头文件中进行函数声明using namespace std; void func(); //函数声明 int main(int argc, char **argv) { func(); return 0; } //函数定义 void func() { cout << "hello function" << endl; }
//example7.cpp #include "example7.h" #includeint main(int argc, char **argv) { func(); // hello world return 0; } void func() { std::cout << "hello world" << std::endl; }
自定义头文件
//example7.h #ifndef __EXAMPLE7_H__ #define __EXAMPLE7_H__ void func(); //函数声明 #endif分离式编译
一个程序可以分为多个cpp文件,也就是将程序的各个部分分别存储在不同文件中。
大致原理是,对多个cpp分别编译,然后将多个编译后的部分进行链接操作形成了整体的程序,虽然在多个cpp中编写,但是我们只有一个全局命名空间,也就是说在多个cpp内定义相同名字的变量这是不被允许的。
example8.cpp
//example8.cpp #include#include "func.h" using namespace std; // int i = 999; //出错因为func.cpp已经定义了int i,不能有重复定义,全局命名空间只有一个 int main(int argc, char **argv) { func(); // hello world return 0; }
func.cpp
//func.cpp #include "func.h" #includeusing namespace std; int i = 999; void func() { cout << "hello world" << endl; }
func.h
#ifndef __FUNC_H__ #define __FUNC_H__ void func(); #endif
分别编译并且最后链接
g++ -c example8.cpp g++ -c func.cpp g++ example8.o func.o -o example8.exe ./example8.exe
或者编译并链接
g++ example8.cpp func.cpp -o example8.exe ./example8.exe参数传递
调用函数时参数的传递分为传引用调用(引用传递)和传值调用(值传递)
传引用//example9.cpp #include为什什么要提供引用传递呢using namespace std; void func(int &i, int *j) { i -= 1; *j -= 1; } int main(int argc, char **argv) { int i = 0, j = 0; func(i, &j);//传递i的引用与j的内存地址 cout << i << " " << j << endl; //-1 -1 return 0; }
对拷贝大的类类型对象或者容器对象,甚至有的类类型对象不支持拷贝,只能通过引用形参支持在其他函数内调用对象,例如有字符串非常长,我们根据操作情况,可以选择引用传递,因为那样省略去了字符串拷贝,节约了内存资源,使得程序效率更高。
//example10.cpp #include使用引用形参返回额外的信息#include #include using namespace std; void func(string &str, vector &vec) { cout << str << endl; for (auto &item : vec) { cout << item << " "; item++; } cout << endl; } int main(int argc, char **argv) { vector v{1, 2, 3, 4, 5}; string str = "hello c++"; func(str, v); // hello c++ 1 2 3 4 5 func(str, v); // hello c++ 2 3 4 5 6 return 0; }
//exaple11.cpp #includeconst形参和实参 关于顶层const的回顾#include using namespace std; int func(int i, string &message) { if (i < 0) { message = "i<0"; return i < 0; } message = "i>=0"; return i < 0; } int main(int argc, char **argv) { string message; func(-1, message); cout << message << endl; // i<0 return 0; }
const int ci = 42;//ci不能被赋值改变,const是顶层const int i=ci;//i可以被赋值,拷贝ci时忽略其顶层const int *const p=&i;//const是顶层的,不能给p赋值 *p=0;//正确,可以改变p的内容,但不能改变p本身存储的内存地址形参的底层const与顶层const
//example12.cpp #include为什么说当实参初始化形参时会忽略掉顶层const?using namespace std; // p同时加底层const与顶层const void func(int i, const int *const p) { cout << i << endl; // 23 cout << *p << endl; // 23 //*p = 99; error: assignment of read-only location '*(const int*)p' // p = nullptr; error: assignment of read-only parameter 'p' } int main(int argc, char **argv) { const int i = 23; func(i, &i); return 0; }
//example13.cpp #include指针或引用形参与constusing namespace std; void func(const int j) { cout << j << endl; // 999 } // void func(int j) // { // } // 'void func(int)' previously defined here // 因为顶层const是相对于函数内部作用而言的,对函数外部都是进行了拷贝 int main(int argc, char **argv) { int num = 999; func(num); //对于外部的调用将忽略形参的顶层const因为有没有const外部都是进行对形参赋值 return 0; }
//example14.cpp #include#include using namespace std; // const int *p=# const string &str=mstr; // p是有顶层const的int指针 str为常量引用 void func(const int *p, const string &str) { string new_str = "hello"; // str = new_str; //错误 因为str为常量的引用 // str = "hello";//错误 因为str为常量的引用 int num = 999; p = # cout << str << endl; } //引用常量 底层const //虽然有这种写法 但是我们好像从不用这种,没有引用常量 // void func(string const &str) // { // cout << str << endl; // str = "hello"; // } int main(int argc, char **argv) { int num = 100; const string mstr = "hi"; func(&num, mstr); // hi string name = "gaowanlu"; func(&num, "oop"); // oop return 0; }
总之 关于const与引用、指针的配和往往会使得我们头大,所以我们还是要多回顾复习以前的变量章节的const的知识
数组的传递总之传递数组就是在传递内存地址
//example15.cpp #include数组形参与constusing namespace std; void func(int arr[]) { for (int i = 0; i < 5; i++) { cout << arr[i] << " "; arr[i]++; } // 1 2 3 4 5 cout << endl; } //重载失败 因为int*p与int arr[]等效 // void func(int *p) // { // cout << sizeof(p) << endl; // } // void func(int arr[5]) // { // } void print(const int *begin, const int *end) { while (begin != end) { cout << *begin << " "; begin++; } cout << endl; } int main(int argc, char **argv) { int arr[5] = {1, 2, 3, 4, 5}; func(arr); // 1 2 3 4 5 func(arr); // 2 3 4 5 6 print(begin(arr), end(arr)); // 3 4 5 6 7 return 0; }
//example16.cpp #include数组引用形参using namespace std; // const int arr[]等价于const int *arr // 底层const可以改变arr存储的地址 不能通过arr改变内存地址上的数据 // 即const int 的指针类型 const int * ,也就是数组的每个数据都是const int void func(const int arr[], size_t size) { size /= sizeof(int); for (int i = 0; i < size; i++) { cout << arr[i] << " "; } // arr[0] = 12; 错误不能改变数组的值 cout << endl; int num = 999; arr = # //*arr = 1000;//error: assignment of read-only location '* arr' } int main(int argc, char **argv) { const int arr[] = {1, 2, 3, 4}; // arr[0] = 1;//error: assignment of read-only location 'arr[0]' func(arr, sizeof(arr)); // 1 2 3 4 int num = 0; int const *p = # //底层const //*p = 999;//error 底层const cout << *p << endl; // 0 return 0; }
//example17.cpp #include传递多维数组using namespace std; void func(int (&arr)[5]) { for (auto item : arr) { cout << item << endl; } } //错误 因为数组的引用必须指定数组的长度 void func1(int (&arr)[], int size) { for (int i = 0; i < size; i++) { cout << arr[i] << " "; } cout << endl; } int main(int argc, char **argv) { int arr[] = {1, 2, 3, 4, 5}; func(arr); int arr1[] = {1, 2, 3}; // func(arr1);//error 数组长度不是5 // func1(arr1, 3); //error 形参没有指定数组的长度 数组的引用必须指定长度 // int(&arr2)[] = arr1;//同理这里也是错误的 // cout << arr2[0] << endl; return 0; }
总之简单的办法就是传递指针,然后也可以使用数组的引用
//example18.cpp #includemain函数的形参using namespace std; // int *matrix[10] 10个指针构成的数组 // int (*matrix)[10] 指向含有10个整数的数组的指针 void func1(int (*arr)[5], int size) { cout << size / sizeof(int) / 5 << endl; // 2 } void func2(int arr[][5], int size) { size = size / sizeof(int) / 5; for (int i = 0; i < size; i++) { for (int j = 0; j < sizeof(arr[i]) / sizeof(int); j++) { cout << arr[i][j] << " "; } cout << endl; } } int main(int argc, char **argv) { int arr[][5] = { {1, 2, 3, 4, 5}, {1, 2, 3, 4, 5}}; func1(arr, sizeof(arr)); func2(arr, sizeof(arr)); // 1 2 3 4 5 1 2 3 4 5 return 0; }
提供了在运行程序时赋值指定实参的功能
//example19.cpp #includeusing namespace std; int main(int argc, char **argv) { for (int i = 0; i < argc; i++) { cout << argv[i] << endl; } return 0; } //输出 aaa bbb ccc
g++ example19.cpp -o example19.exe ./example19.exe aaa bbb ccc可变形参
C++11有新特性,在我们无法提前预知向函数传递几个实参,在C++11中,如果所有的实参类型相同,可以传递initializer_list类型,如果实参的类型不相同可以编写特殊的函数,所谓的可变参数模板。
还有一种特殊的形参类型即省略符,可以用来传递可变数量的实参,不过一般这种功能只用于C函数交互的接口程序。
initializer_list形参initializer_listlst; 默认初始化;T类型元素的空列表 initializer_list lst{a,b,c}; lst的元素数量和初始值一样多:lst的元素是对应初始值的副本,列表中的元素是const lst2(lst) lst2=lst 拷贝或赋值一个initializer_list对象不会拷贝列表中的元素;拷贝后,原始列表和副本共享元素 lst.size() 列表中的元素数量 lst.begin() 返回指向lst中首元素的指针 lst.end() 返回指向lst中尾元素下一位置的指针
使用样例
//example20.cpp #include省略符形参#include #include using namespace std; void mfun(initializer_list list) { for (auto beg = list.begin(); beg != list.end(); beg++) { cout << beg << " " << *beg << " "; } cout << endl; } int main(int argc, char **argv) { mfun({"1", "2", "3"}); // 0x61feb0 1 0x61fec8 2 0x61fee0 3 initializer_list params{"1", "2", "3", "4"}; mfun(params); // 0x61fe48 1 0x61fe60 2 0x61fe78 3 0x61fe90 4 // params[0];//error initializer_list不支持下标访问 auto list1(params); //拷贝params对象 但是它们的元素的内存是共用的 mfun(list1); // 0x61fe48 1 0x61fe60 2 0x61fe78 3 0x61fe90 4 auto list2 = list1; mfun(list2); // 0x61fe48 1 0x61fe60 2 0x61fe78 3 0x61fe90 4 //总之initializer_list的元素是只读的 return 0; }
省略符形参只能出现在形参列表的最后一个位置
总之它的作用就是可以当用户输入参数多的时候我们可以省略,但不影响函数的正常运行。
//example21.cpp #includeusing namespace std; void fun1(int num1, int num2, ...) { cout << num1 << " " << num2 << endl; } void fun2(...) { cout << "fun2" << endl; } int main(int argc, char **argv) { fun2(1, 2, 3, 4); // fun2 fun1(1, 2); // 1 2 return 0; }



