ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

C++ Primer小记 第六章 函数

2021-02-19 22:58:47  阅读:130  来源: 互联网

标签:const 函数 形参 int void C++ Primer 指针 小记


第六章 函数

6.1 函数基础

函数的形参列表

​ 不带形参的函数,两种定义方法

void f1(){}			//隐式定义
void f2(void){}		//显式定义

6.2 参数传递

6.2.1 传值参数

指针形参

​ 指针的行为和其他非引用类型一样。当执行指针拷贝操作时,拷贝的是指针的值。拷贝之后,两个指针是不同的指针。因为指针使我们可以间接的访问它所指的对象,所以通过指针可以修改它所指对象的值。

//Q:了解指针形参 
void reset(int *p){
	*p = 50;
	cout << "形参p的值:" << p << endl;
    p = 0;
    cout << "p=0执行后,形参p的值:" << p << endl;
}

void e1(){
	int i = 42;
    cout << "i的初始值:" << i << endl; 
    int *q = &i;
    cout << "实参q的值:" << q << endl;
    reset(q);
    cout << "函数运行过后i的值:" << i << endl; 
    cout << "函数运行过后实参q的值:" << q << endl; 
} 

以前很长一段时间都把指针形参等同于引用形参,这是错误的。要注意区分指针形参和引用形参的区别。指针形参还是值传递,引用形参是引用传递。

6.2.2 传引用参数

使用引用避免拷贝

​ 拷贝大的类类型对象或者容器对象比较低效,甚至有的类类型根本不支持拷贝。当某种类型不支持拷贝操作时,函数只能通过引用形参(指针形参不行吗?)访问该类型的对象。

​ 如果函数无需改变引用形参的值,最好声明为常量引用***(const type &i)***。

使用引用参数返回额外信息

​ 想让函数返回多个值,引用形参为我们一次返回多个结果提供了有效的途径。下面总结一下返回多个值的方法。

  • 定义一个新的数据类型,比如XX类。返回这个类,这个类里面有你要的那些值
  • 返回的是某个对象,还想返回其他东西,则形参定义引用,然后通过引用改变外面那个值(实参),最后在函数外使用实参即可得到。
  • 返回类型void,全部使用指针或者引用来完成

6.2.3 const形参和实参

void fcn(const int i){}		//fcn能读取i,但不能像i写值
void fcn(int i){}			//错误,重复定义fcn(int)

​ 上述代码看起来好像是重载,但是利用实参初始化形参时,如果形参有顶层const,传给它的常量对象或者非常量对象都是可以的。那么,当主调函数在调用fcn的时候,用的实参都是非常量对象,那编译器就无法区分到底应该调用哪一个fcn了,因为两个都可以匹配。

6.2.4 数组形参

//一维
void print(const int*);
void print(const int[]);
void print(const int[10]);
void print(int (&arr)[10]);		//数组引用形参

​ 对于多维数组来说,数组的第二维(以及后面所有维度)的大小都是数组类型的一部分,不能省略。

//多维
void print(int (*matrix)[10],int rowSize);		//第二维是10个int的数组,其实这么看*(matrix[10])更好理解
void print(int matrix[][10],int rowSize);

6.2.5 main:处理命令行选项

prog -d -o ofile data0
int main(int argc,char *argv[]){}
int main(int argc,char **argv){}

​ 当实参传给main函数之后,argv的第一个元素指向程序的名字或者一个空字符串,接下来的元素依次传递命令行提供的实参。最后一个指针之后的元素值保证为0

​ 如上面命令行,argc为5

argv[0] = "prog";	//argv[0]保存程序的名字,而非用户输入
argv[1] = "-d";
argv[2] = "-o";
argv[3] = "ofile";
argv[4] = "data0";
argv[5] = 0;

6.2.6 含有可变形参的函数

initializer_list形参

​ 如果函数的实参数量未知但是全部实参的类型都相同,即可用它。注意包含头文件***<initializer_list>***。

​ initializer_list对象中的元素永远是常量值。无法改变initializer_list对象中元素的值。

//Q:initializer_list形参 -- 只有initializer形参 
void error_msg(initializer_list<string> li){
	for(const auto &elem : li){		//还可以使用迭代器来实现 
//		elem = "okay";				//试图改变initializer_list对象中的元素,编译报错。因为initializer_list对象中的元素是常量 
		cout << elem << endl;
	}
} 

void e3(){
	string i = "i is false", j = "j is true";
	if(i == j){
		error_msg({"i == j",i,j,"okay"});
	}else{
		error_msg({"i != j",i,j});
	}
}

//Q:initializer_list形参 -- 还拥有其他形参 
void error_msg(int code,initializer_list<string> li){
	cout << "Err_code is " << code << endl;
	for(auto begin = li.begin(),end = li.end(); begin != end ; ++begin){
		cout << *begin << endl;
	}
}

void e4(){
	string i = "i is false", j = "j is true";
	if(i == j){
		error_msg(520,{"i == j",i,j,"okay"});
	}else{
		error_msg(404,{"i != j",i,j});
	}
}

省略符形参

​ 一般用于与C函数交互的程序接口。这些代码使用了名为varargs的C标准库功能。

​ 省略符形参只能出现在形参列表的最后一个位置,他的形式无外乎两种:

void foo(param_list,...){}
void foo(...){}

​ 在第一种形式中,形参声明逗号后面是可选的。

​ 省略号的优先级别最低,所以在函数解析时,只有当其它所有的函数都无法调用时,编译器才会考虑调用省略号函数的。

由于还涉及到一些新的类型和方法书上没有细说,以后再仔细研究

6.3 返回类型和return语句

​ return有两种形式。

return ;
return expression;

6.3.1 无返回值函数

​ 没有返回值的函数只有void返回值类型的函数。这种函数最后无需显式的写return语句,因为在这类函数的最后一句后面隐式的执行return。

​ 当然,void返回值类型的函数也可以显式的写 return expression 类型的语句。但是,此时return语句的expression必须是另一个返回void的函数(这句其实调用了一个函数)。强行令void函数返回其他类型的表达式将产生编译错误。

6.3.2 有返回值函数

主函数main的返回值

​ 6.3.1里面说过如果函数返回值类型不是void,那它必须返回一个值。除了main函数。如果main函数没有return语句直接结束,编译器将隐式的插入一条返回0的return语句。

​ 为了使返回值和机器无关,***cstdlib***头文件定义了两个预处理变量。

int main(){
    if(flag){
        return EXIT_FAILURE;	//定义在cstdlib头文件中
    }else{
        return EXIT_SUCCESS;	//定义在cstdlib头文件中
    }
}

递归

​ main函数不能调用他自己。

6.3.2 返回数组指针

使用类型别名的方法来定义

typedef int arrT[10];		//或者 using arrT = int[10];
arrT* func();				//返回一个指向含有10个整数的数组的指针

返回数组指针的函数,普通定义

Type (*function(parameter_list))[dimension]

​ 注意要有括号,如果没有括号,将会返回指针的数组

​ 下面举个具体的例子:

int ( ***** func(int i))[10];

  • func(int i) 表示调用func函数时需要一个int类型的实参。
  • (***** func(int i)) 意味着我们可以对函数调用结果执行解引用操作。
  • (***** func(int i))[10] 表示解引用func的调用将得到一个大小是10的数组。
  • int (***** func(int i))[10] 表示数组中的元素是int类型。

使用尾置返回类型

​ 尾置返回类型跟在形参列表后面并以一个 -> 符号开头。为了表示函数真正的返回类型跟在形参列表之后,我们在本应该出现返回类型的地方放置一个auto

auto func(int i) -> int( ***** ) [10];

​ 注意这个带括号的解引用符,表示func函数返回的是一个指针,并且改指针指向了含有10个整数的数组。

使用decltype

​ 如果我们知道函数返回的指针将指向哪个数组,就可以使用decltype关键字声明返回类型。

int odd[] = {1,3,5,7,9};
int even[] = {2,4,6,8,0};
//返回一个指针,该指针指向含有5个整数的数组
decltype(odd) *arrPtr(int i){
    return (i%2) ? &odd : &even;	//返回一个指向数组的指针
}

​ 这里涉及到一个知识点,***decltype***对于数组类型的解析,并不会把数组类型转换成对应的指针。所以*decltype的结果是一个数组。因此需要一个来表示返回的是数组指针。

6.4 函数重载

​ 如果同一个作用域内的几个函数名字相同但形参列表不同,我们称之为重载函数。

main函数不能重载。

​ 对于重载函数来说,他们应该在形参数量或形参类型上有所不同。

重载和const形参

​ 前面说过如果形参是顶层const,实参可以是常量也可以是非常量。因此,像下面这种情况:

int findNum(Phone);
int findNum(const Phone);

不是重载,因为const是顶层const,实参可以是常量也可以是非常量。还有下面这种情况:

int findNum(Phone*);
int findNum(Phone* const);

​ Phone* const 说明指针是个常量。是顶层const。因此这也不是重载。

​ 当然如果形参是底层const,这样构成重载。因为如果实参是常量,不能传给非底层const的形参,因为可以传的话就相当于可以用形参来改变常量,不符合逻辑。而实参不是常量的话,看起来形参是不是底层const都可以。但是一旦有这种重载,编译器就会区分,如果是非常量编译器会优先选择非常量版本,如果是常量只能选择常量版本。

int findNum(Account&);
int findNum(const Account&);

​ 构成重载,因为第一个不是常量引用,第二个是常量引用,底层const。记住,对于引用来说,不管const怎么放,都是底层const。

int findNum(Account*);
int findNum(const Account*);

​ 同上一个类似,因为const Account* 是指向常量的指针,是底层const。

const_cast 和重载

​ 通过下面的例子来了解用法。前面提过const_cast,是用来显式转换的,只能改变运算对象的底层const。

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);
}

​ 其实上面两个函数,因为形参列表不同且第一个函数const不是顶层const,所以是重载函数。这里主要是展示当需要普通string的时候,如何通过普通的非常量实参,去调用函数。并且返回的也是非常量实参。

​ 利用const_cast来实现,当在第二个函数中调用第一个函数时,利用其把非常量转换成常量,最后返回一个常量引用。注意这个r其实是对s1或s2的一个引用,只是在这里是常量引用,这是符号逻辑的。由于第二个函数最后需要的是非常量引用,因此可以利用const_cast再次转换,然后返回。

6.5 特殊用途语言特性

6.5.1 默认实参

​ 一旦某个形参被赋予了默认值,它后面所有形参都必须有默认值。

int find(int ht = 24 , int wd = 80 , char backgrnd = '');

​ 想要覆盖backgrnd的默认值,必须为ht和wd提供实参。

默认实参声明

​ 同一个函数可以多次被声明,但是在给定作用域中一个形参只能被赋予一次默认实参。换句话说,函数的后续声明只能为之前那些没有默认值的形参添加默认实参,并且该形参右侧的所有形参都必须有默认值(一旦某个形参被赋予了默认值,它后面所有形参都必须有默认值)。

​ 假如给定

int find(int a,int b,char c = '');

​ 我们不能修改一个已经存在的默认值:

int find(int a,int b,char c = '*');		//错误,重复声明

​ 但是可以按照如下形式添加默认实参:

int find(int a = 24,int b = 80,char c);

默认实参初始值

局部变量不能作为默认实参

//Q:默认实参初始值案例
//A:局部变量不能作为默认实参,其他可以,但是要注意位置。 
int ht(){
	return 55;
}

int wd = 100;
char def = 'a';

int myFind(int ht = ht(), int wd = wd, char def = def){
	cout << " ht = " << ht		
			<< " wd = " << wd
			<< " def = " << def << endl;
}

void e5(){
	wd = 200;			//wd值被改变,由于改变的是全局变量wd,且在调用之前改变的,所以默认实参也就改变了 
	char def = 'b';		//局部变量def被改变,并不影响默认实参,局部变量不能作为默认实参 
	myFind();
} 

6.5.2 内联函数和constexpr函数

内联函数可以避免函数调用的开销

​ 在函数的返回类型前面加上关键字***inline***,这样就可以将它声明成内联函数了。内联说明只是向编译器发出的一个请求,编译器可以选择忽略这个请求。一般来说内联函数用于规模小、流程直接、频繁调用的函数。所以如果函数声明为内联函数,但是规模较大,编译器很可能只是把他当成普通函数来处理。

constexpr函数

​ 定义constexpr函数的方法和其他函数类似,不过要遵循几项约定:

  • 函数的返回类型及所有形参的类型必须是字面值类型(算术类型,引用和指针都属于字面值类型)

  • 函数中必须有且仅有一条***return***语句

    ​ constexpr被隐式的指定为内联函数。

    ​ 我们允许constexpr函数的返回值并非一个常量。

    constexpr int scale(int cnt){
        return 42 * cnt;
    }
    

    ​ 当实参是常量表达式的时候,他的返回值也是常量表达式,反之则不然

    int arr[sacle(2)];		//正确,scale(2)是常量表达式
    int i = 2;
    int a[scale(i)];		//错误,scale(i)不是常量表达式
    

    ​ 记得前面之前说过,constexpr函数就是为了编译的时候就得到结果。而且得到常量的结果。但是constexpr函数不一定返回常量表达式。上面的例子,如果需要的是常量表达式,那就得返回常量表达式,否则就会报错。

    内联函数和constexpr函数放在头文件

    ​ 和其他函数不同,内联函数和constexpr函数在程序中可以多次定义。但是多个定义必须完全一致。所以内联函数和constexpr函数通常定义在头文件中。

    ​ 在这里突然有点疑问。其他函数只能定义一次,内联函数和constexpr函数可以多次定义但必须完全一致,那和一次定义有什么区别吗?为什么这里强调通常把他们定义在头文件中。

    ​ 想了一个解释,因为内联函数和constexpr函数一般来说要较小的函数,所以放在头文件中比较合适。而一般的函数由于要用到很多东西,有时还要使用到对应文件的数据,所以在程序中定义较合适。

6.7 函数指针

​ 函数的类型由它的返回类型和形参类型共同决定,与函数名无关。

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

​ 该函数类型是***bool (const string &,const string &)***。声明一个指向函数的指针,只需要用指针替换函数名即可,bool ( ***** ***pf)(const string &,const string &)***。

使用函数指针

//赋值
pf = lengthCompare;
pf = &lengthCompare;		//和上面是等价的,取地址符是可选的

//使用,无需提前解引用
bool b1 = pf("hello","goodbye");
bool b2 = (*pf)("hello","goodbye");			//等价调用
bool b3 = lengthCompare("hello","goodbye");	//另一个等价调用

注意,不同函数类型的指针不存在转换规则。使用上述的***pf***,只能用于***bool (*** ***** ***pf)(const string &,const string &)***类型的函数。

//Q:函数指针类型及调用的案例
//A:函数指针只能指向同类型的函数,不能转换。 
void func1(int a){
	cout << "func1被调用" << endl;
} 

void func2(int b){
	cout << "func2被调用" << endl;
}

void e6(){
	void (*p)(int) = func1;	//定义函数指针,且指针初始化指向func1 
	p(1);					//通过函数指针的方式调用函数,没有用解引用符 
	p = func2;				//给函数指针重新赋值,这是同类型赋值 
	(*p)(2);					//通过函数指针的方式调用函数,用解引用符 
}

重载函数的指针

​ 注意一点,使用重载函数的指针,必须清楚的界定用哪个函数。因为重载函数形参列表不同,使用重载函数的类型不同,则重载函数的指针就不同。

函数指针形参

​ 函数指针可以作为形参。

void userFuncPointer(void (*pf)(int),int x){		
	pf(x);
} 

​ 当然上面那么写比较麻烦,可以使用别名替代。如下:

//Q:函数指针形参案例
//Func 和 Func2 是函数类型 
typedef void Func(int);
typedef decltype(func1) Func2;	//等价的类型 
//FuncP 和 FuncP2 是指向函数的类型
typedef void (*FuncP)(int);
typedef decltype(func1)* FuncP2;  //等价的类型 
void userFuncPointer(FuncP2 pf,int x){		//FuncP2换成上面任意一种都可以 
	pf(x);
} 

void e7(){
	userFuncPointer(func1,1);
	userFuncPointer(func2,2);
} 

​ 注意一点,decltype返回函数类型,此时不会将函数类型自动转换成指针类型,需要自己加指针,如FuncP2。

返回指向函数的指针

​ 注意是返回指向函数的指针,不能返回函数。不使用类型别名的情况写函数***f1***,返回的是一个函数指针。

int (*f1(int))(int*,int);

​ 从内向外看

  • ***f1(int)***是一个函数。

  • *f1前面有一个。说明要返回指针

  • 这个指针有形参列表,说明返回的是函数指针

  • 最开头的那个***int***,说明函数指针指向的函数,返回类型是***int***

    上述写法较麻烦,可以使用别名。

using F = int(int*,int);		//F是函数类型,不是指针
using PF = int(*)(int*,int);	//PF是函数指针

​ 注意返回的是函数指针,使用返回类型应该是 PF 或者 F***** ,不是***F***。

将auto和decltype用于函数指针类型

​ 注意一点,decltype返回函数类型,不是函数指针

标签:const,函数,形参,int,void,C++,Primer,指针,小记
来源: https://blog.csdn.net/qq_43243107/article/details/113873408

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有