赞
踩
内容包括:返回值,函数名,0至多个参数的列表和函数体
调用函数的两项工作:
调用return时返回值用于初始化调用表达式的结果,控制权回到main
void f1() {} //隐式
void f2(void) {} //显示
返回类型不能是数组类型或者函数类型,但可以是指向数组或函数的指针。
形参和函数体定义的变量都称为是局部变量
块执行期间的对象叫自动对象
函数开始时为形参申请空间,函数终止形参消亡
在程序的执行路径上第一次经过对象定义语句时初始化,并且直到程序终止才消亡,因此函数结束也不会对它有影响
#include <bits/stdc++.h>
using namespace std;
size_t count_c() {
static size_t cnt = 0;
return ++cnt;
}
int main() {
for (int i = 0; i != 10; ++i)
cout << count_c() << endl;
return 0;
}
///会输出1到10
可只有声明没有定义
void print(int a, int b);
建议把函数放在头文件里声明源文件定义
一个项目只能有一个main函数,这个函数就是开始执行的函数
所有的传参都跟新定义变量赋值一样
如同:新定义了形参
int a, b; //假设这是实参
int c = a, *d = b, &e = b; 这是形参
所以c改变不了a,但d,e可以
不支持拷贝的类型可以通过引用来访问对象
当无需修改值时用引用比较省内存
可以利用引用参数当多余需要的返回值,因为函数只能返回一个值
当形参时const时,会忽略掉顶层const
void fun(const int i ){}
void fun(int i) {} //错误上面定义过了
我们可以使用非常量初始化一个底层const,但反过来就不行
vector<int>::iterator ff(const int &k, vector<int>::iterator i) {
for (int j = 0; j < k; ++j, ++i) {
cout << *i << endl;
cin >> *i;
}
return i;
} //这个也能更改容器里的元素
特性:不能拷贝数组,数组通常会将其换成指针所以传数组就是传头指针
void print(const int*);
void print(const int[]);
void print(const int[10]);//上面三个等价,这里的维度是我们期望数组含有多少,实际不一样
int i = 0, j[2] = {0, 1};
print(&i);
print(j); //都可以因为j转换成&j[0];
使用时必须保证不能越界
字符数组本身可以做结束标记符
char cp[] = "sdad";
if (*cp) //若cp不是空字符
管理数组的好方法就是传首尾字符的指针
void print(const int *beg, const int *end) {
while (beg != end)
cout << *beg++ << endl;
}
int j[2] = {0, 1};
print(begin(j), end(j));
///或者可以选择传人数组维度
允许变量定义成数组引用,形参也行
void print(int (&arr)[3]) {
for (auto i : arr)
cout << i << endl;
}//是可以的
首元素的本身就是数组,指针就是一个指向数组的指针。所以第二维的维度大小也是数组类型的一部分,不能省略
void print(int (*arr)[3], int rowsize) { //指向由3个数组元素构成的数组指针
}
void print(int arr[][3], int rowsize) {} //等价
主函数是可以传参的,
prog -d -o ofile data0
int main(int argc, char *argv[])//
argc: 字符串的数量
argv: 字符串数组
按照执行输入的顺序输入你的argv
当不确定传入实参的数量时,c++11新标准提供了两种主要方法:如果所有的实参类型相同,可以传递一个名为initializer_list 的标准类型;
如果实参的类型不同,我梦编写一种特殊函数可变参数模板
c++还有一种特殊形参类型省略符
,可以用它表示可变数量的实参
一般用于与c函数交互的接口程序
标准库类型用于表示某种特定类型的值的数组定义在同名的头文件里
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xen02zyc-1583289906115)(image-20200303110043895.png)]
和vector一样时模板类型
要定义所含元素类型
void error_msg(initializer_list<string> il) {
for (auto beg = il.begin(); beg != il.end(); ++beg)
cout << *beg << " ";
cout << endl;
}
error_msg({"nihao", "sda"});
元素永远是常量无法更改
传递值得序列,必须放入花括号中
void error_msg(int e, initializer_list<string> il) {
cout << e << ": ";
for (auto &i : il) {
cout << i << " ";
}
cout << endl;
}
string as = "sddad", bs = "sdada";
error_msg(4,{as, bs});
为了c++访问某特殊得c代码设置得,这些代码用了varargs的c标准库
大多数类类型对象无法通过省略符传递形参正确拷贝
只能出现在形参列表的最后一个位置
省略号对应的类型无需检测
void foo(parm_list, ...);//指定部分形参类型,后面的逗号是可选
void foo(...); //
无返回值void型时有隐式return,可不用加return结束
有返回值,返回的类型必须与函数返回的类型一致或隐式转换成返回类型
和初始化一个变量或形参的方式完全相同:返回值用于初始化调用点的一个临时量,该临时量就是函数调用的结果。
const string &shortstring(const string &s1, const string &s2) {
return s1.size() < s2.size() ? s1 : s2;
} //形参和返回类型都是const string 的引用,不管是调用函数还是返回值都不会真正的拷贝对象。
int *pp() {
int k = 5;
return &k; //错误引用局部变量
}
调用一个引用的函数得到左值,其他右值
string &sot(string &s1, string &s2) { return s1.size() <= s2.size() ? s1 : s2; } string as = "ddad", bs = "sdada"; string &k = sot(as, bs); k += "100"; cout << as << endl;// 这个能更改as /-------------------------------------------------------------- char &get_va(string &str, string::size_type ix) { return str[ix]; //假定索引有效 } int main() { string s("a val"); cout << s << endl; get_va(s, 0) = 'A'; cout << s << endl; /// 输出A val return 0; }
列表初始化返回值
c++11 新标准, 函数可以返回花括号包围的值的列表。可以为类似其他返回结果
用来对函数返回的临时量进行初始化
vector<string> process() {
return {"","sds"};
}
函数调用了自身,函数调用自身是会用到程序栈空间的
数组不能拷贝,所以函数返回数组只能通过数组指针或者引用
typedef int arrT[10]; //arrT 是一个类型别名,他表示的类型是含有10个
using arrT = int[10]; //arrT等价声明
arrT* func(int i); //func 返回一个指向10个整数的数组指针
也可以直接声明:
int arr[10];
int *p1[10];
int (*p2)[10] = &arr; // p2是个指针,它指向含有19个整数的数组
维度必须确定
所以函数的为:
int (*func(int i))[10];
func(int i) 表示需要一个int类型的实参
(*func(int i)) 意味着我们可以对函数调用的结果执行解引用
int (*func(int i))[10] 表示解引用得到的是一个大小为10的数组,数组的元素是int型的
c++11新标准中还有一种可以简化上述func声明的方法就是尾置返回类型
任何函数定义都可以用,对于复杂的最有效
在形参列表后面并以一个->符号开头。为了表示函数真正的返回类型,类型的位置放auto
写法如下:
auto func(int i) -> int(*)[10]; 表示和上面的等价
auto func(int (*a)[3]) -> int(*)[3] {
(*a)[1] = 3;
return a;
}
int a[] = {0, 1, 2};
func(&a);
使用decltype
通过函数返回的指针指向哪个数组,就可以用此关键字的返回类型
int odd[] = {1, 3, 5, 7, 9};
int even[] = {0,2,4,6,8};
decltype(odd) *arr(int i) {
return (i%2) ? &odd : &even;
}
main函数不能重载
名字相同但形参列表不同的我们称为函数重载
虽然调用定义相同的函数名,但编译器会根据实参的类型确定调用的函数
void print(const char *cp);
void print(const char *beg, const int *end);
...
* 编译器会根据传入实参类型去选择
定义重载函数
typedef int in;
void pp(int i);
void pp(in i); //错误 :这两个是相同的所以不行
double pp(in i); //错误,形参一样
string pp(const int i); //错误普通形参类型不区分const
string pp(const int *i);
string pp(int *i); //可以 引用和指针可以区分const //非常量的对象或指针,会优先选用非常量版本的函数
void pp(double i); //这个可以类型不同
# const_cast的用法
const string &ff(const string &s) {
return s;
}
string &ff(string &s) {///利用它进行转换,实现与上面相同的功能且返回普通的string&是安全的
auto &r = ff(const_cast<const string&>(s));
return const_cast<string&>(r);
}
作用域里出现的同名类型会隐藏掉外界的重载函数
string read();
void print(const string &);
void print(double);
void foof(int ival) {
bool read = false;//新作用域,隐藏了外层的read
read();//错误 read被隐藏了,不是函数
void print(int); //新作用域,隐藏外层的print
print("vv");//错误
print(ival); //正确 print(int);可见
print(3.1); //正确转为int调用print(int);
}
某系函数的形参可含有默认实参,在调用的时候可以省略该实参
string sc(int h, int k = 55, char s= ' ');
一个形参用了默认值后面的必须要用
//调用
string k;
k = sc();//错误h形参没有默认值
k = sc(3);//正确等价于 sc(3, 55, ' ');
k = sc(3, 33);//正确等价于 sc(3, 33, ' ');
k = sc(3, 22, '#');//正确等价于 sc(3, 22, "#");
k = sc(3, , 's'); //错误只能省略尾部实参
添加默认形参 同个形参只能被添加一次
int fs(int, int, int = 3);
int fs(int, int, int = 3);// 错误重复定义
int fs(int, int = 1, int); //正确添加为 fs(int, int = 1, int = 3);
int ks = 4;
int a = 5;
int fs(int = a, int = ks); //ks 和a必须在函数体外
void f2() {
ks = 3;
int kk = fs(); //调用了fs(5, 3);
}
调用函数其实包含一系列工作:调用前要保存寄存器,并返回时恢复;可能需要拷贝实参;转向另一个位置继续执行
内联函数可避免函数开销
inline const string & shorterString(const string &s1, const string &s2) {
return s1.size() <= s2.size() ? s1:s2;
}
cout << shorterString(s1, s2) << endl;
cout << (s1.size() <= s2.size())? s1 : s2 << endl;等价
constexpr 函数
函数返回类型和形参类型都是字面类型
且必须包含return语句
constexpr int new_sz() {return 33;}
constexpr int foo = new_sz(); //正确foo是常量表达式
编译器能验证new_sz 的返回值是常量表达式
为了能在编译过程中随时打开, constexpr函数会隐式指定为内联函数
函数体内不能另外声明,但可以,有空语句,类型别名,using声明等
constexpr size_t scl(size_t cnt) {return new_sz()*cnt;}
int arr[scl(2)]; //正确:scl(2)是常量表达式
int i = 2;
int a2[scl[i]]; //错误scl(i) 不是常量;虽然我的编译器没有报错
头文件保护的技术,以便选择地执行调试代码,当应用编写完成准备发布时要先屏蔽调试代码。
这种方法用到两项预处理功能: assert 和NDEBUG
assert
:是一种预处理宏,其实就是一个预处理变量类似内联函数。用表达式作为它的条件
assert(expr);
先对expr求值,如果表达式为假,assert 输出信息并终止程序的执行。 如果非0为真,assert什么都不做
在头文件cassert中
int lin = 5;
assert(lin == 0);//会终止程序
//Assertion failed: lin == 0, file D:\工作缓存区\git\C--Depth_review\函数\fun\main.cpp, line 79
NDEBUG 预处理变量
assert 的行为依赖域一名为NDEBUG的预处理变量的状态。如果定义了NDEBUG,则assert什么都不做,默认没有定义时,assert会执行运行时检测
我们可以语句定义NDEBUG ,关闭调试状态。
CC -D NDEBUG main.c
等价与main.c里写#define NDEBUG >>要放在最开始
或者如果NDEBUG未定义
void print(const int ia[], size_t size) {
#ifndef NDEBUG
//_ _func_ _是编译器定义的一个局部静态变量,用于存放函数名
//是一个const char 的一个静态数组, 存放函数名
cerr << __func__ <<" :array size is " << size << endl;
#endif // NDEBUG
}
名字 | 含义 |
---|---|
__FILE__ | 存放文件名的字符串 |
__LINE__ | 存放当前行号的整型字面值 |
__TIME__ | 存放文件编译时间的字符串 |
__DATE__ | 存放文件编译日期的字符串 |
用于描述错误信息 |
在大多数情况下,我们容易确定某次调用应该选用哪个重载函数
确定候选函数和可行函数
候选函数集
函数名同名,声明调用点可见
可行函数
形参数量与实参数量一致,实参类型与形参类型相同,或者能转换形参类型
double ss(int);
double ss(double, double = 5.2);
ss(5.3); //会调用第二个,因为有默认实参,且用double不会损失精度,如果没有第二个,就会调用第一个
void ffs(int a, int b) {
}
void ffs(double a, double b) {
}
ffs(3,3.2); //错误二义性,系统无法确定哪个脱颖而出
为确定最佳匹配,转换划分等级,排序如下:
精确匹配
通过const转换实现匹配
如果是引用或指针是否用const ,则通过实参是否常量来选择
通过类型提升实现匹配
通过算术类型转换
如:一个接收int,一个接收short,则只有当提供short才选择short,即便是很小的整数
从int转unsigned int 并不比int 转double 高,
void man(long);
void man(float);
man(3.1); //二义性
通过类类型转换
函数指针指向的是函数而非对象,函数类型由他的返回类型和形参类型共同决定
bool lens(const string &, const string &);
bool (*p)(const string &, const string &);// 为初始化与函数指针
使用函数指针
当我们把函数名做一个值来用时,该函数会自动转换成指针,
p = lens; //pf指向lens函数 p = &lens; //等价,取地址符可选的 //使用指针调用函数时,可以无需解引用 bool b1 = p("ss", "ssa"); //调用lens函数 bool b2 = (*p)("ss", "ssa"); //等价 bool b2 = lens("ss", "ssa"); //等价
- 1
- 2
- 3
- 4
- 5
- 6
重载函数指针
当我们使用重载函数时,必须清晰定好选用的函数
void ff(int *);
void ff(unsigned int );
void (*pf)(unsigned int) = ff; //pf 指向第二个
函数形参
和数组类似,虽然不能定义函数类型的形参,但时可以为指向函数的指针
void uses(const string &s1, const string &s2, bool pf(const string &, const string &));
//第三个类型是函数类型,他会自动转换成函数指针
void uses(const string &s1, const string &s2, bool (*pf)(const string &, const string &)); // 等价
uses(s1, s2, lens); //可以直接把函数当实参使用,会自动转换
可利用decltype简化
typedef bool Func(cosnt string &, const string &);
typedef decltype(lens) Func2;
//函数等价类型
typedef bool (*Fun2)(cosnt string &, const string &);
typedef decltype(lens) *Fun1; //指针等价类型
返回指向函数的指针
和数组相似
using F = int(int *, int ); //F是一个函数类型
using PF = int(*) (int *, int ); //PF是指针类型
返回类型并不能自动转成地址,所以要显示的返回指针类型
PF f1(int); //正确
F f1(int); //错误。 F是函数类型
F *f1(int); // 正确
int (*f1(int))(int *, int); //正确
auto f1(int) -> int (*)(int*, int);//正确
#include <bits/stdc++.h> using namespace std; using F = int(*)(int, int); typedef int (*F1)(int, int); int add(int a, int b) { cout << __func__ << " :"; return a+b; } int sub(int a, int b) { cout << __func__ << " :"; return a-b; } int mult(int a, int b) { cout << __func__ << " :"; return a*b; } int divi(int a, int b) { cout << __func__ << " :"; return a/b; } typedef decltype(add) *F2; int main() { int a, b; cin >> a >> b; vector<F> ve; ve.push_back(add); ve.push_back(sub); ve.push_back(mult); ve.push_back(divi); for (auto &i : ve) cout << i(a,b) << endl; return 0; }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。