基础
函数
函数编写
- 执行函数时首先定义并初始化形参,当遇到renturn语句时参数结束执行过程。返回retur语句中的值并将控制权从被调函数转移回主调函数。
returntype functionname(para1,para2,...){statement}
- 函数形参列表不能省略,可以写为
void f1(){}
void f2(void){} //与C语言兼容
int fact(int val){
int ret = 1;
while(val>1){
ret*=val--;
}
return ret;
}
函数调用
- 使用调用运算符,形式是一对圆括号,作用与一个表达式,该表达式时函数或指向函数的指针。圆括号之内是用逗号隔开的实参列表。
- 调用主要实现实参初始化函数的形参,并将控制权转移给被调用函数。
int main(){
int j = fact(5);
cout<<j<<endl;
return 0;
}
局部对象
- 名字有作用域,对象有生命周期。
- 局部变量指形参和函数体内部定义的变量。分为自动对象和局部静态对象。
- 自动对象指函数经过变量定义时创建,到达定义所在尾块时销毁。
- 局部静态对象指变量生变周期贯穿函数调用以及之后的时间。第一次经过变量定义时初始化,知道程序终止才销毁。
int size_calls() {
static int cnt = 0; //只初始化一次
return ++cnt;
}
int main() {
for (int i = 0; i < 5; ++i) {
cout << size_calls() << endl; //输出1 2 3 4 5
}
return 0;
}
函数声明
- 函数声明也称函数原型,函数名字需要在使用之前声明,函数声明无需函数体,用分号替代。函数声明包含函数三要素(返回类型、函数名、形参类型),描述函数接口。
- 建议函数在头文件中声明。
void print(vector<int>::const_iterator beg,vector<int>::const_iterator end);
参数传递
- 引用,实参被引用传递或函数被传引用调用
- 拷贝赋值,实参被值传递或函数被传值调用
传值
- 对传入形参的改动不影响实参初始值;
- 传入指针时,若形参指针地址内的值发生改变,可以改变对应指针地址里的值。实参指针地址不会发生改变。
int count_prtcalls(int* n) {
int ans = 1;
for (int i = 1; i <= *n; ++i) {
ans *= i;
}
cout <<"n:" << ans << endl; //1
++n;
ans = 1;
for (int i = 1; i <= *n; ++i) {
ans *= i;
}
cout <<"++n:" << ans << endl; //2
cout << "++n:" << *n << endl;//2
*n = 100;
cout << "++n:" << *n << endl;//100
return ans;
}
int main() {
int n[] = {1,2,3,4,5};
int* p = n;
count_prtcalls(p);
cout << *p << endl; //仍然指向1
++p;
cout << "++p:" << *p << endl; //也变为100
return 0;
}
传引用
- 通过引用传参,可以实现多个实参值改变;
- 对于大的类类型对象以及不支持拷贝的对象,建议通过引用形参的访问该类型对象。
- 当无需改变对象内容时,可以定义成常量引用。
//统计string中字母a出现次数以及位置
void alphacount(int& cnt, vector<int>&pos, const char& c, const string& s)
{
for (decltype(s.size()) i = 0; i < s.size(); ++i) {
if (s[i] == c) {
++cnt;
pos.push_back(i);
}
}
}
int main() {
string s;
cout << "input string: ";
cin >> s;
int cnt = 0;
vector<int>pos;
char c = 'a';
alphacount(cnt, pos, c, s);
cout << "count:"<<cnt << endl;
cout << "position:";
for (decltype(pos.size()) i = 0; i < pos.size(); ++i) {
cout << pos[i] << " ";
}
cout << endl;
return 0;
}
const形参与实参
- 形参是顶层const时,实参初始化形参会忽略掉顶层const。顶层const可以忽略,注意程序编写时重复定义的问题。
void fcn(const int i)
void fcn(int i) //重复定义
- 对于形参是指向const类型的引用或指针时(底层const),常不能初始化非常。
- 建议把不会改变的形参定义为常量引用,一方面提醒调用者该形参不能更改,另一方面可以将const对象、字面值以及需要类型转换的对象传给形参。
数组传参
- 数组特点:不允许拷贝,使用通过指针调用。
void print(const int* arr);//数组元素只读不写
void print(int* arr);//数组元素需要读写
void print(int(&arr)[10]);//数组的引用,引用的是指针,不是数组所以要加括号
void print(const int arr[10]); //维度表示实际希望数组元素数,可不填
void print(const int(*arr)[10],int rowsize);//int**,指向含有10个整数数组的指针
数组尺寸
- 标记指定:如字符串数组里的’\0‘。
- 传递首和尾后指针。
函数形参可变
编写能处理不同数量实参的方法:
- 实参类型相同:传递标准库类型initializer_list
- 实参类型不同:编写可变参数模板
- 省略符
initializer_list形参
- 实参数量未知,全部实参类型相同。
- 头文件:
#include<initializer_list>
- 操作
initializer_list<T> lst; //类型T的空列表
initializer_list<T> lst{a,b,c,...}; //lst的元素是对应初始值的副本,列表中的元素是const
lst2(lst) //拷贝或赋值initializer_list对象,不拷贝列表里的元素,拷贝后原始列表和副本共享元素
list.size()
lst.begin()
lst.end()
- 定义时,需要说明列表所含元素类型
- initializer_list对象中元素永远是常量值,不能改变。
- 可以用begin()和end()获得指针,来进行列表内数据处理
void error_msg(int i, initializer_list<string>ls) {
cout << i << ":" << endl;
for (auto i = ls.begin(); i!=ls.end(); ++i) {
cout << *i << endl;;
}
}
void error_msg(int i, initializer_list<int>ls) {
int sum = 0;
cout << i << ":" << endl;
for(auto i = ls.begin(); i != ls.end(); ++i) {
sum += *i;
}
cout << sum << endl;
}
int main() {
error_msg(1, { "apple","hhdy","zwhy" });
cout << endl;
error_msg(2, {1,2,3,4});
return 0;
}
省略符形参
- 仅用于C和C++通用类型,便于C++访问某些特殊C代码而设置,使用了名为varargs的C标准库功能。
- 大多数类类型对象在传递给省略符形参时都无法正确拷贝。
- 省略符形参只能出现在形参列表的最后一个位置
void foo(parm_list,...);
void foo(...);
返回类型和return语句
return
- 中止当前正在执行的函数并将控制权返回调用该函数的地方
- 无返回值函数可以隐式推出。有返回值的函数只能通过一条有效return函数退出,函数循环后也要加return。
return; //void类型
return expression; //void类型可以用来返回void函数,或非void类型
调用运算符
- 优先级:调用运算==点运算符==箭头运算符。
返回函数引用
- 返回函数引用是左值,返回其他类型是右值。
char &val(string &str,string::size_type index) {
return str[index];
}
int main() {
string s("apple");
val(s, 0) = 'A';
cout << s << endl; //Apple
return 0;
}
- 不要返回局部对象的引用或指针,局部字面值也不可以(向当局局部临时对象)。
const string &val(string& n) { //想返回局部对象的引用
string str= "hhdy";
if(str == "hhdy"){
return str; //局部对象的引用,漏!
}
return "hhdy"; //漏!
return n; //ok
}
列表初始化返回值
- 可以返回花括号包围的列表,类似列表初始化。
- 对于内置类型,列表内最多包含一个值,所占空间小于目标类型空间。对于类初始化,看类初始化中列表初始化方法。
vector<int> getval(){
return{1,2,3};
}
int main(){
vector<int>ans = getval();//相当于{1,2,3}
return 0;
}
主函数main返回值
- 允许main没有return语句直接结束,会隐式加上“return 0;”。
- main函数返回值可以看做是状态指示器。若像使返回值和机器无关,cstdlib头文件定义了两个预处理变量:
return EXIT_FAILURE; //1
return EXIT_SUCCESS; //0
递归
- main 函数不能调用自己
返回数组指针
指向数组的指针
//类型别名using
int arr[10];
using arrT = int[10];
arrT* p = &arr;
//类型别名 typedef
typedef int arrT1[10];
arrT1* p1 = &arr;
//不使用类型别名
auto p2 = &arr;
int (*p3)[10] = &arr;
decltype(arr)*p4 = &arr;
类型别名返回数组指针
//类型别名using
using arrT = int[10];
arrT* func(int i);
//类型别名 typedef
typedef int arrT[10];
arrT* func(int i);
不使用类型别名返回数组指针
int (*func(int i))[10];
auto func(int i)->int(*)[10];
int odd[10];
decltype(odd) *func(int i);
函数重载
定义
- 同一作用域内存在几个函数名字相同但形参列表不同(包括形参数目及类型),称为函数重载。
- main函数不能重载
const形参
- 要区分重载和重复声明,对于const类型,顶层const形参与没有顶层const的形参区分不开。底层const可以区分开。
//这两个会报错,redefination
void foo(const int a){}
void foo(int a){}
//这两个不会
void foo(const int*p){}
void foo(int *p){}
//这两个会报错
void foo(int *const p){}
void foo(int *p){}
const_cast
- 可以使用const_cast实现常量引用到非常量引用的重载。
const string &shorterString(const string &s1, const string &s2) {
return s1.size() <= s2.size() ? s1 : s2;
}
string &shorterString(string &s1, string &s2) {
auto &r = shorterString(const_cast<const string &>(s1), const_cast<const string &>(s2));
return const_cast<string &>(r);
}
int main() {
const string s1 = "11";
const string s2 = "222";
string s3 = "33";
string s4 = "444";
string &s5 = shorterString(s4, s3);
const string &s6 = shorterString(s1, s2);
cout << s5 << endl; //33
cout << s6 << endl; //11
}
函数匹配
- 重载调用的函数选择方法
第一步,找重载函数集,与被调用函数同名且调用点可见其声明。
第二步,找可行函数,实参数目与形参相等,且类型相同或能转换。注意由默认实参时,输入实参数量可以小于形参数量。
第三步,寻找最佳匹配,实参形参越接近则匹配的越好。如果出现实参存在两个最优匹配重载函数,则会报告二义性,拒绝其请求。
- 实参到形参转化优先级
1精确匹配:类型相同、数组函数转到相应指针、添加或删除实参顶层const
2const转换实现匹配
3类型提升
4算数类型、指针转换
5类类型转换
- 对于类型提升,所有算数类型转换级别一样高
void func(long);
void func(float);
func(3.14) //3.14是double型,转成long和float级别一样高,出现二义性
重载作用域
- 如果在内层作用域中声明名字,它将隐藏外层作用域中声明的同名实体。不同作用域中无法重载名字。
string read(string s);
void print(const string &s);
void print(double);
int main() {
bool read = false;
void print(int);
print(1);
print("s"); //输入应为int型
string s = read("s"); //read被重载为bool型
}
默认实参
- 为了使窗口既接纳默认值,也能接纳用户指定的值,可以对形参进行默认值定义。
- 可以为多个形参进行默认值定义,但一旦形参被赋予默认值,后面所有形参都必须有默认值。
- 对于省略的形参,会使用默认实参
- 局部变量不能作为默认实参。只要表达式类型能转换成形参所需的类型,该表达式就能作为默认实参。
int val(int n, int m = 4, int k = 1) {
return n * m * k;
}
int main() {
int ans = val(8); //32
int ans1 = val(1, 2); //2
int ans2 = val(1, 5, 6); //30
cout << ans << " " << ans1 << " " << ans2 << endl;
}
- 应在函数声明中指定默认实参
int val(int n, int m = 4, int k = 1);
内联函数
- 考虑到函数调用一般比求等价表达式的值慢一些,可以使用内联函数避免函数调用的开销。
- 内联机制用于优化规模小、流程直接、频繁调用的函数。很多编译器不支持内联递归函数,对函数长度也有要求。
- 内联只是向编译器发出一个请求,编译器可以选择忽略这个请求。
- 通常定义在头文件内
inline int val(int n, int m = 4, int k = 1) {
return n * m * k;
}
constexpr函数
- 指能用于常量表达式的函数
- 返回类型及所有形参必须是字面值类型
- 函数体中必须只有一条return语句,只进行一次返回
- constexpr在编译过程中,被隐式指定为内联函数
- constexpr函数不一定返回常量表达式
- 通常定义在头文件内
constexpr int new_sz() {
return 42;
}
int main() {
constexpr int n = new_sz();
return n;
}
调试帮助
用于调试代码
assert预处理宏
- assert作为预处理宏,是预处理变量(不需要std::,不需要using声明所在空间),宏名字在程序内唯一。
- 通常用来检查"不能发生"的条件。
- 头文件
#include<cassert>
- 如果表达式为假(0),assert输出信息并终止程序运行;如果表达式为真(非0),assert什么也不做。
assert(expr);
int new_sz(int i) {
assert(i<100);
if (i > 9)
return 42;
else
return 10;
}
int main() {
int n = new_sz(101);
return n;
}
NDEBUG预处理变量
- assert行为依赖名为NDEBUG的预处理变量状态,定义了NDEBUG,assert什么也不做;没有定义,执行检查。
- 通过预定义NDEBUG可以关闭assert调试。
#define NDEBUG
#define NDEBUG //整个程序最开头写
#include<iostream>
#include<cassert>
using namespace std;
int new_sz(int i) {
assert(i<100);
if (i > 9)
return 42;
else
return 10;
}
int main() {
int n = new_sz(101);
return n; //返回42
}
- 也可以使用NDEBUG编写自己的代码调试,如果未定义#NDEBUG,执行#ifndef到#endif之间的程序。如果定义了#NDEBUG则忽略#ifndef到#endif之间的程序。
//#define NDEBUG
#include<iostream>
#include<vector>
#include<string>
#include<cassert>
using namespace std;
void new_sz() {
#ifndef NDEBUG
cerr << __func__ << " have error at " << __LINE__
<<" in "<<__FILE__<<endl
<< "compile on " << __DATE__ << " at " << __TIME__ << endl;
#endif
}
int main() {
new_sz( );
return 0;
}
注:c++编译器定义:
__func__ //当前调试函数
__FILE__ //当前文件名
__LINE__ //当前行号
__TIME__//文件编译时间
__DATE__//文件编译日期
函数指针
-
指向函数的指针。类型由返回类型和形参类型共同决定。
-
首先创建一个函数:
bool lengthCompare(const string &,const string &){}
声明方式
- 用指针替换函数名进行该函数声明
bool (*pf)(const string &,const string &); //指向返回值是bool类型函数的指针
bool *pf(const string &,const string &); //返回值是bool类型指针
使用
- 把函数名作为一个值使用,该函数自动转换成指针。指向不同函数类型或形参类型数目不同时,不存在转换规则。
pf = lengthCompare;
pf = &lengthCompare;
函数调用
- 使用指向函数的指针调用该函数,无需提前解引用指针:
bool b1 = pf("bad","apple");
bool b2 = (*pf)("bad","apple");
bool b3 = lengthCompare("bad","apple");
函数指针作为形参:
- 形参可以是指向函数的指针
bool lengthCompare(const string& s1, const string& s2) {
return s1.size() > s2.size() ? 1 : 0;
}
bool len(const vector<int>& n1, const vector<int>& n2, bool pf(const string& s1, const string& s2)) { //函数指针的声明可以在函数里
cout << pf("abdee", "cdfr") << endl; //可以直接在函数里使用函数指针
if (pf("ab","cdfr"))
return n1.size() > n2.size() ? 1 : 0;
else
return 1;
}
bool (*pf)(const string&, const string&);
bool (*pf1)(const vector<int>& n1, const vector<int>& n2, bool pf(const string& s1, const string& s2));
int main() {
pf = lengthCompare;//实际调用可以使用函数名传参进去,他会自动转化为指针
pf1 = len;
bool b1 = pf("bad", "apple");
bool b2 = (*pf)("bad", "apple");
bool b3 = lengthCompare("bad", "apple");
bool b4 = pf1({ 1,2 }, { 1,2,3 }, pf);
cout << b1 << " " << b2 << " " << b3 <<" "<<b4<< endl;
return 0;
}
返回指向函数的指针
- 使用类型别名
using F = int(int*,int);
using PF = int(*)(int*,int);
typedef int(*PFF)(int*,int);
typedef int(FF)(int*,int);
PF f1(int); //PF是指向函数的指针,f1返回指向函数的指针
F *f1(int);
PFF f1(int);
FF* f1(int i)
int (*f1(int))(int*,int);
- auto
auto f1(int) ->int(*)(int*,int);
- decltype
//当已知指向函数的类型时
int PF1 (int* a, int b)
//可以使用decltype
decltype(PF1)* f1(int i)
using namespace std;
using F = int(int*, int);
using PF = int(*)(int*, int);
typedef int(*PFF)(int*, int);
typedef int(FF)(int*, int);
int PF1 (int* a, int b){
return 1;
}
int PF2(int* a, int b) {
return 2;
}
PF f1(int i) {
if (i == 1)
return PF1;
else
return PF2;
}
F* f2(int i) {
if (i == 1)
return PF1;
else
return PF2;
}
PFF f5(int i) {
if (i == 1)
return PF1;
else
return PF2;
}
FF* f6(int i) {
if (i == 1)
return PF1;
else
return PF2;
}
int (*f3(int i))(int* a, int b) {
if (i == 1)
return PF1;
else
return PF2;
}
auto f4(int i) ->int(*)(int* a, int b) {
if (i == 1)
return PF1;
else
return PF2;
}
decltype(PF1)* f7(int i) {
if (i == 1)
return PF1;
else
return PF2;
}
int main(){
int a = 1;
int* b = &a;
cout << f1(1)(b, a) << endl;
cout << f2(1)(b, a) << endl;
cout << f3(1)(b, a) << endl;
cout << f4(1)(b, a) << endl;
cout << f5(1)(b, a) << endl;
cout << f6(1)(b, a) << endl;
cout << f7(1)(b, a) << endl;
return 0;
}