primer学习笔记-第六章函数

六、函数

一个命名了的代码块

1.函数包含:返回类型、函数名、形参列表、函数体;

形参:在函数参数列表中声明的局部变量,它们由每个函数调用中提供的参数初始化,作用是说明函数参数的类型。

实参:函数调用中提供的值,用于初始化函数的参数。

调用运算符,表达式是函数或者指向函数的指针,圆括号内是实参列表,用于初始化形参;

2.调用函数:实参相同或可以转化为形参的类型

3.形参列表:每个形参都含有一个声明符的声明,及时两个形参的类型一样

4.函数的返回类型不能是数组类型或函数类型

函数的类型必须与函数的返回类型相匹配!

6.1.1局部对象

名字的作用域,对象的生命周期;

1.自动对象(形参):块执行结束后,块中创建的自动对象的值就变成未定义的了。

2.局部静态对象:令局部变量的生命周期贯穿函数调用及之后的时间。(比如计数值使用)

局部变量:定义在一个块中的变量。

形参:在函数参数列表中声明的局部变量,函数终值,形参也被销毁。

局部静态变量:在第一次执行通过对象定义之前初始化局部静态变量(对象)。函数结束时不破坏局部静态;它们在程序终止时被销毁。

6.1.2函数声明

函数原型

1.声明中可以省略形参的名字,但是写上可以帮助理解

2.与变量一样,建议将函数的声明放在头文件,而在源文件中定义

6.1.3分离式编译

可以把程序分割到几个文件中,每个文件单独编译

6.2参数传递

形参是引用类型,则绑定到对应的实参上;否则,将实参的值拷贝后赋值给形参

引用传递--传引用调用;值传递--传值调用

例:引用传递交换值(C++最好使用引用类型的形参代替指针,修改实参的变量


#include <iostream>
using namespace std;
 
int exchange(int &val1, int &val2)//(引用传递)
{
	int a;
	a = val1;
	val1 = val2;
	val2 = a;
	return 0;
}
void main()
{	
	cout<<"请输入需要交换的两整数:";
	int val1,val2;
	cin>>val1>>val2;
	cout<<"交换之前的两数:"<<val1<<" "<<val2<<endl;
	exchange(val1,val2);
	cout<<"交换之后的两数:"<<val1<<" "<<val2<<endl;

1.有些拷贝大的类类型对象或容器对象比较低效,可以直接用引用形参访问;如果也无须改变对象的内容,可以把形参定义为对常量的引用;

2.使用引用形参返回额外信息:给函数传入一个额外的引用实参;

3.const形参和实参:实参初始化形参时,会忽略掉顶层const。

当形参有顶层const时,传给它常量对象或者非常量对象都是可以。

复习:const引用可能引用一个非常量、字面值、一般表达式,可以通过其他途径改变值,但是普通的引用不能绑定到const引用上;(第二单元const限定符)

1.可以用非常量初始化一个底层const对象,二反过来不行;

2.同时一个普通的引用必须用同类型的对象初始化

void str(int &i)
{
    i=0;
}
int i=0;
const int ci=i;
string::size_type ctr=0;
reset(&i);//正确
reset(&ci);//错误,不能用指向const int的对象初始化指正int*
reset(i);
reset(ci);//错误,不能把普通引用绑定到const对象上
reset(45);//引用不能绑定字符值
const(ctr);//类型不匹配

4.尽量使用常量引用:

1.不改变的形参定义为普通引用时常见错误,带来对函数调用者的误导;

2.使用引用而非常引用也会极大限制函数所能接受的实参类型

练习6.17

#include <iostream>
#include <string>
 
using std::string;
 
bool hasUppercase(const string& str)
{
    for (auto c : str)
        if (isupper(c)) return true;
    return false;
}
 
const string& makeLowercase(string& str)
{
    for (auto& c : str)
        if (isupper(c)) c = tolower(c);
    return str;
}
 
int main()
{
    string str("Hello World!");
    std::cout << std::boolalpha << hasUppercase(str) << std::endl;
    std::cout << makeLowercase(str) << std::endl;
}

5.数组形参:

数组特征:1.不允许拷贝数组;2.使用数组时会将其转化为指针

函数传递一个数组时,实际上传递的是指向数组首元素的指针(不能用值传递,用引用传递)!

管理指针形参的方法:

1.使用标记指定数组长度:C风格字符串存储式是,空字符(结束标记)

2.使用标准库规范:传递数组首元素和尾后元素的指针begin()和end()函数

3.显示传递一个表示数组大小的形参;

形参可以是数组的引用,绑定到数组上

  1. void f(int &a[10]);//引用的数组

  2. void f(int (&a)[10]);//数组的引用

	void print(int(&arr)[10])//限制了维度
	{
		for (auto s : arr)
			cout << s << endl;
	}

当函数不需要对数组元素进行写操作时,使用const常量指针。

6.main:处理命令行选项

需要给main()函数传递实参,之前写的程序基本上main()函数都是空形参列表

特殊点:使用argv中的实参时,一定要记得可选参数从argv[1]开始,argv[0]保存的是程序的名字。

#include <iostream>
#include <string>
 
int main(int argc, char** argv)
//实参列表,其中第二个argv是指针数组,相当于char *argv[ ]
{
    string str;
    for (int i = 1; i != argc; ++i) {
        str += argv[i];
        str += " ";
    }
 
    cout << str <<endl;
    return 0;

7.含有可变形参的函数

为了编写处理不同数量实参的函数

1.实参数量未知但是类型一致:

      可以使用initializer_list类型的形参(标准库类型,定义在同名头文件)

      表示某种特定类型的值的数组;与vector一样,一种模板类型;元素永远是常量值

void error_msg(initiallizer_list<string> il)
{   
   for (auto beg=il.begin();beg!=il.end();++beg)
      cout<<*beg<<" "<<endl;
}

     向initializer_list形参中传递一个值的序列,则必须把序列放在一对花括号内

error_msg({ "functionX",expected,catual });
error_msg({ "functionX","Okay"});

2.省略符形参

  便于c++访问某些特殊的c代码而设置的,使用名为varargs的c标准库功能。

 两种形式:

void foo(parm_list,...);
void foo(...);

6.3返回类型和return语句

1.void 最后有隐式执行return,可以不写;中间位置可用于提前退出

6.3.2有返回值函数

返回的值用于初始化调用点的一个临时量,该临时量就是函数调用的结果。

1.不要返回局部变量的引用或指针(函数完成后,专用的存储空间释放):

返回的引用无效:局部临时变量或者局部对象的引用对于返回都是无效的,因为在函数终止之后,局部变量或者对象的引用不再指向有效的内存区域。若是常量在函数调用之前存在,引用即可用。

2.调用运算符的优先级与点运算符、箭头运算符相同,符合左结合律:

auto sz=ShortString(s1,s2).size();//先函数调用返回值,然后取对象中的size()成员

3.函数的返回类型决定函数是否是左值。调用一个返回引用的函数是左值(可以出现在赋值左侧),其他返回类型是右值。

4.列表初始化返回值

5.main函数可以省略return 0

6.递归:函数调用了她自己:求阶乘

	int factorial(int val)
	{
		if (val > 1)
			return factorial(val - 1) * val;
		return 1;
	}

输出vector对象内容:

#include <iostream>
#include <vector>

using std::cout;
using std::endl;
using std::vector;
using Iter = vector<int>::iterator;

void print(vector<int>::iterator beg, vector<int>::iterator end)
{
    if (beg != end) {
        cout << *beg << " ";
        print(++beg, end);
    }
    else
    {
        cout << endl;
        return;
    }
}

int main()
{
    vector<int> vec{ 1, 2, 3, 4, 5, 6, 7, 8, 9 };
    print(vec.begin(), vec.end());
}

6.3.3返回数组指针

函数可以返回数组的指针或引用

声明一个返回数组指针的函数:int (*fun(int i))[10];

1.使用类型别名

2.任何函数都可以使用尾置返回类型:对于返回类型是数组的指针或数组的引用有效;

auto func(int i) ->int(*)[10];//返回一个指针,该指针指向含10个整型的数组

3.使用decltype,已知函数的返回值时,可以使用关键字decltype表示返回类型为指针

string (&func(string (&arrStr)[10]))[10]  //返回数组的引用,数组包含10个string对象

using ArrT = string[10];
ArrT& func1(ArrT& arr);//使用类型别名

 auto func2(ArrT& arr) -> string(&)[10]; //使用尾置返回类型
 
string arrS[10];
decltype(arrS)& func3(ArrT& arr);//使用decltype关键字

  6.4函数重载

同一作用域内几个函数名字相同但形参列表不同。

1.不允许两个函数除了返回类型外其他所有的要素相同;

2.顶层const不影响传入函数的对象,形参有无const是同一个函数;

3.底层const可以实现函数重载;

最好只重载那些确实非常相似的操作,给函数起不同的名字使函数更好理解!

4.可以用const_cast,非常量转换为常量

5.重载与作用域:如果在内层作用域声明名字,将隐藏外层作用域中声明的同名实体。在不同作用域中无法重载函数名

6.5特殊用途语言特性

6.5.1默认实参

含有默认实参的函数,默认实参负责填补函数调用缺少的尾部参数(靠右侧位置);

一旦函数的某个形参被赋予了默认值,他后面所有的参数都必须有默认值;(从右到左默认实参。从左到右赋值)

1.给定作用域中一个形参只能被赋予一次默认形参;后续声明只能对没有默认值的形参添加

2.局部变量不能作为默认变量

3.只要表达可以被转换成形参所需的类型,就能作为默认实参(如char变int)

6.5.2内联函数和constexpr函数

调用函数比一般求等价表达式慢一些。

内联函数:将它在每个调用点上“内联地”展开,在函数返回类型前加关键词inline

用于规模较小、流程直接、调用频繁的函数,有些编译器不支持。

constexpr函数

能用于常量表达式的函数。

要求:函数的返回类型及形参的类型都是字面值类型,而且函数体有且仅有1个return语句

通常将内联函数和constexpr函数定义在头文件中。

constexpr int new_sz() {return 42;}

6.5.3调试帮助

用于调试的代码,只在开发使用,发布时要屏蔽掉。

assert预处理宏:

一种预处理宏(预处理变量,行为与inline函数类似)

预处理宏assert(expr):包含一个表达式,expr为真时,assert什么也不做,为假时输出信息并终止程序

1.包含在cassert头文件中;

预处理名字由预处理器而非编译器管理,可以直接用预处理名字声明。

宏名字在程序内必须唯一

2.通常用于检查不能发生的条件

NDEBUG预处理变量:

定义后,避免检查各类条件所需的运行时开销;

2.可以使用NDEBUG编写自己的条件调试代码,如果NDEBUG未定义,将执行#ifndef到#endif之间的代码,如果定义了NDEBUG,这些代码将被忽略。

 #ifndef NDEBUG
		cerr<<"my name is:"<<__func__<<endl;
    #endif
	//其他代码

一些C++编译器定义的调试有用的名字:
_ _func_ _ :一个静态数组,存放函数的名字

_ _FILE_ _ :存放文件名的字符串字面值

_ _LINE_ _ :存放当前行号的整形字面值

_ _TIME_ _ :存放文件编译时间的字符串字面+值

_ _DATE_ _ :存放文件编译日期的字符串字面值

6.6函数匹配

候选函数:函数匹配的第一步是选定本次调用的重载函数集,集合中的函数被称为候选函数

可行函数:第二步,根据实参情况,从候选函数中挑选出能被这实参调用的函数,此次选出的函数被称为可行函数。

第三部,寻找最佳匹配:精度匹配要比类型转换好。

1.实参类型到形参转换(p219)

在匹配过程中,除了精确匹配还有几种转换类型的匹配方式。
引用类型的const,类型转换:判别方式是判断实参是否是常量来决定选择哪个函数。指针类型的形参判别也是类似的。(虽然非常量对象初始化常量引用可以,但需要类型转换)

类型提升:不是完全相同,皆会提升类型,char会提升为int。

算术类型转换:该转换的级别都相同,即3.14是double,转换成long或者float都是同样等级,会产生二义性。

这几种匹配是有顺序的。从上到下

6.7函数指针

1.指向函数,用指针替换函数名,括号必须写

bool lengthCompare  (const string &, const string &);

bool (*p) (const string &, const string &);

2.函数名作为一个值使用,该函数自动转换为指针;

可以直接使用指向函数的指针调用函数,无须提前解引用指针:

bool b2= p( "hello" , "goodbye" );//也可以用(*p)或者 lengthCompare

3.不同函数类型指针不存在转换规则,可以将函数指针赋nullptr

4.重载函数的指针:指针类型与重载函数中某一个精确匹配;

5.指向函数的指针可以作为函数的形参:

void useBigger(const string &str1, const string &str2, bool pf(const string &,const string &)) ;//pf
void useBigger(const string &str1, const string &str2, bool *pf(const string &,const string &)) ;//*pf
void useBigger(const string &str1, const string &str2, lengthCompare) ;//lengthCompare

注意:
使用decltype返回的函数类型 decltype(Func2)
typdef  bool  Func(const string &, const string &) ;

加上*,返回指针 typdef  bool  (*Func) (const string &, const string &);

6.返回函数的指针:
类型别名的方法定义

using F= int (int*,int); F *f1(int);//函数别名定义,定义函数指针
using *F=int (int*,int); F f1(int);//函数指针别名定义
int (*f1(int))(int*,int);//形参表f1是函数,*f1指针,指针本身包含形参表函数指针
auto f1(int) -> int (*)(int*,int);//尾置定义返回

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值