首页 > 编程语言 >【C++ Primer】函数

【C++ Primer】函数

时间:2023-10-10 10:01:41浏览次数:42  
标签:const 函数 形参 int void C++ Primer string


    函数是一个命名了的代码块,通过调用函数执行相应的代码,函数可重载。

 

一、函数基础

实参初始化形参。return有两项工作:1、返回值(如果有的话);2、将控制权从被调函数转移回主调函数。

 

int fact(int val)
{
    int ret = 1;
    return ret > val ? ret : val;
}
int main()
{
    int j = fact(5);
    return 0;
}

实参的类型必须与对应的形参类型匹配(或者可以实参可以隐式转换为形参),数量也必须匹配(除非提供了默认实参);

 

 

 

【Note】:

    1)C++中的函数必须有返回类型,不需要返回值时可以指定其类型为void,只能有一个返回值。

    2)形参列表可以为空(显式(void)或者隐式()),但是不能省略,任意两个形参都不能同名。

    3)函数返回类型不能是数组和函数,但是可以返回指向数组或者函数的指针。

 

1、局部对象

名字有作用域,对象有生命周期。形参和函数体内定义的变量统称为局部变量。仅在函数的作用域内可见,存储在栈上,函数结束时被销毁。因此不要返回局部对象的引用或者指针。局部变量如果没有初始化则会产生未定义的值。

    自动对象:只存在于代码块({...})执行期间的对象,执行时才分配内存。形参是一种自动对象。

令局部变量的声明周期贯穿函数调用之后的时间,可以将局部变量定义为static类型。static对象一直都在内存中,每次都被初始化为上次调用的值。内置类型的局部静态变量初始化为0,只能使用常量表达式初始化局部静态变量。

 

int global = 100;//局部静态变量,外部链接性(可以在其他文件中使用)。
static int a = 50;//局部静态变量,内部链接性(只能在本文件中使用)。
void fun()
{
    //局部静态变量,没有链接性(在其他地方被使用),一直在内存中。
    static int cnt = 0;//每次都被初始化为上次调用的值。
    //局部自动变量,没有链接性,运行时分配内存。
    int b = 1;
    return ++cnt;
}
int main()
{
    for (size_t i =0 ; i < 10 ; ++i)
	cout << fun() << endl;//计算函数被调用了多少次。
    return 0;
}

 

2、函数声明

 

含有函数声明的头文件(.h)应该被包含到定义函数的源文件(.cpp)中。

 

二、参数传递

    形参初始化机理与变量初始化一样。

 

1、传值参数

初始值被拷贝给变量。当指针作为参数时,拷贝的是指针的值。

 

void reset(*p)
{
	*ip = 0;//改变ip所指的值。
	ip = 0;//ip的局部拷贝被改变了,不会影响实参。
}

int i = 42;
reset(&i);//改变i的值。
cout << i << endl;//输出0。

2、传引用参数(建议使用此方式)

 

    拷贝大的类型对象或者容器对象比较低效,甚至有的类型不支持拷贝,此时只能通过引用传递参数。

 

void reset(int &i)
{
	i = 0;//改变i的值。
}

int i = 42;
reset(i);
cout << i << endl;//输出0。

【Note】:

 

最好将其声明为常量引用。

返回“多个值”(例如返回字符出现的总次数及其位置),此时可以在函数形参列表中添加一个引用参数。

 

string::size_type find_char(const string &s,char c,string::size_type &occurs)
{
	/***/
}

 

 

3、const形参和实参

因此const普通形参不可以重载:

 

void fun(const string s,char c) { /***/ }
void fun(string s,char c) { /***/ }

因此const引用或者指针形参可以重载:

 

 

void fun(const string &s,char c) { /***/ }
void fun(string &s,char c) { /***/ }

【Note】:
    1)尽量使用常量引用,可以提高程序的效率。

 

 

 

 

4、数组形参

不允许拷贝数组;使用数组名字时会将其转换为指针。使用数值时注意不要越界。

 

void fun(const int*);
void fun(const int[]);//传递数组时,实际上是传递指向首元素的指针。
void fun(const int[10]);//数组的大小对函数调用没有影响。

int i = 0,j[2] = {0,1};
fun(&i);
fun(j);

    管理数组的方式:标准库(begin,end)、显示传递一个表示数组大小的形参(size_t等)。

传递多维数组,实际上传递的是指向首元素的指针:

 

void fun(int (*matrix)[10]);

 

 

5、main:处理命令行选项

 

int main(int argc, char *argv[]) { /***/ }
int main(int argc, char **argv) { /***/ }

argv是一个指针数组(每个元素都是指针),它的第一个元素指向程序的名字或者一个空字符串,接下来的元素依次传递命令行提供的实参。第一个参数argc表示数组中字符串的数量。例如:

 

 

prog -d -o ofile data0

 

    argv[0]保存程序的名字,可选参数从argv[1]开始。

 

6、含有可变参数的函数

    为了能处理不同数量实参的函数,C++11提供了两种方法:

实参数量未知,但是类型相同,可以传递一个名为initializer_list【C++11】的标准库类型(类似于容器)。

可变参数模板。

 

void error_msg(ErrCode e,initializer_list<string> il)//可以拥有其他形参。
{
	cout << e.msg() << ": ";
	for (const auto &elem : il)
		cour << elem << " ";
	cout << endl;
}

if(expected != actual)//expected和actual都是string对象。
{
	error_msg(ErrCode(42),{"FunctionX",expected,actual});//必须使用列表初始化。
}
else
{
	error_msg(ErrCode(0),{"FunctionX","ok"});
}

 

三、返回类型和return语句

 

1、无返回值函数

    返回值为void的函数,可以没有return语句(被隐式执行)。

 

2、有返回值函数

    return语句返回值的类型必须与函数的返回类型相同,或者能隐式地转换为函数的返回类型。只能通过一条有效的return语句退出。

引用返回的是左值。

 

char &fun(string &s,string::size_type ix)
{ 
    return s[ix];
}

string s("abc");
fun(s,0) = 'A';//如果是常量引用,就不能赋值。
cout << s ;//输出Abc。

    【Note】:

不要返回局部对象的引用或者指针。函数结束时,局部对象所占用的空间也随之消失,所以局部对象的引用和指针都指向了不存在的空间,因此会发生内存错误。

如果函数的返回类型不是void,则必须有返回值(main函数除外)。

main函数不能递归调用。

 

3、返回数组指针

因为数组不能被拷贝,所以函数不能返回数组。但是可以返回数组的指针或者引用。

 

#include <bits/stdc++.h>
using namespace std;
int odd[] = {1,3,5,7,9};//使用全局数组,存储在全局/静态存储区。
int even[] = {0,2,4,6,8};
auto func(int i) -> int(*)[5]//使用尾置类型返回
{
    return (i % 2) ? &odd : &even;
}
int main(int argc, char const *argv[])
{
	int (*arr)[5];//arr是一个指针,指向含有5个元素的数组。
	arr = func(3);//其实arr也是一个**类型。
	for (int i=0 ; i<5 ; ++i)
		cout << (*arr)[i] << " ";
	system("pause");
	return 0;
}

    任何函数的定义都能使用尾置类型返回【C++11】,这种形式对于返回类型比较复杂的函数最有效。比如返回数组的指针或者引用。
 

 

四、函数重载

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

    【Note】:

    1)main函数不能重载。

2)最好只重载那些确实非常相似的操作。

    3)在C++的编译器中,如果出现了函数重载和函数带有默认参数(可以省略,所以会和函数重载冲突)时,编译是会出错的。

 

1、重载的几种方式

正确的重载:

    1)形参列表的类型或者数量不同:

 

int lookup(int a);
int lookup(double b);
int lookup(int a,double b);

    2)如果形参类型是指针或者引用,那么通过区分是常量对象或者非常量对象可以实现重载:

 

 

int lookup(const int &a);
int lookup(int &a);
int lookup(const int *);
int lookup(int *);

错误的重载:
    1)不允许除返回类型外其他的要素都相同,即返回类型不能作为判断依据(C++有时候会忽略返回类型):

 

 

bool lookup(int a);
int lookup(int a);

    2)形参的名字不能作为判断依据:

 

 

int lookup(const int &a);
int lookup(const int &);

    3)顶层const不能作为判断依据:

 

 

 

 

int lookup(int a);
int lookup(const int a);

     使用函数匹配来调用重载的函数,可能有三种结果:最佳匹配、无匹配、二义性调用。

 

 

 

 

 

2、重载与作用域

 

#include <bits/stdc++.h>
using namespace std;
string read();
void print(const string &);
void print(double);
void foobar(int val)
{
	bool read = false;//隐藏了外层的read。
	string s = read();//错误:read是bool是类型。
	void print(int);//在局部作用域内声明函数不是一个明智的选择。
	print("value");//错误:print(const string &)被隐藏掉了。
	print(val);//正确:当前print(int)可见。
	print(3.14);//正确:调用print(int),print(double)被隐藏掉了。
}

    【Note】:
    1)在C++语言中,名字查找发生在类型检查之前。

 

 

 

如果我们在内层作用域中声明名字,将隐藏外层作用域中声明的同名实体。

    3)在局部作用域内声明函数不是一个明智的选择。

函数可以嵌套声明,可以嵌套调用,但是不能嵌套定义。

 

五、特殊用途语言特性

1、默认参数

反复出现的值称为默认实参。

 

using string::size_type sz;
string screen(int x, int y, sz ht = 24, char bg = ' ');

screen(1,2);//等价于screen(1,2,24,' ');

    【Note】:

 

 

 

所以尽量把默认实参放在后面。

    2)只能省略尾部的实参。

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

    4)通常,应该在函数声明中指定默认参数,并将该声明放在合适的头文件中。

 

2、内联函数

    将函数指定为内联函数,通常就是将它在每个调用点上“内联地”展开(非拷贝)。

 

inline const string &ShortString(const string &s1,const string &s2)
{
	return s1.size() <= s2.size() ? s1 : s2;	
}

内联机制用于优化规模较小、流程直接、频繁调用的函数。
 

 

3、constexpr函数

用于常量表达式的函数,函数的返回类型及所有形参的类型都必须是字面值类型。

constexpr函数在编译期就能获得结果,编译器把对constexpr函数的调用替换成其结果值。constexpr不一定返回常量表达式。尽量把一些简单、容易得到结果的函数作为constexpr函数。

 

constexpr int new_sz() { return 42; }
constexpr int foo = new_sz();

    把内联函数和constexpr函数放在头文件中。

 

 

 

 

 

4、assert预处理宏

    assert是一种预处理宏,assert使用一个表达式作为其条件。

 

assert(expr);

 

这个宏通常原来判断程序中是否出现了明显非法的数据,如果出现了终止程序以免导致严重后果,同时也便于查找错误。

以下是使用断言的几个原则:

(1)使用断言捕捉不应该发生的非法情况。不要混淆非法情况与错误情况之间的区别,后者是必然存在的并且是一定要作出处理的。
(2)使用断言对函数的参数进行确认,每个assert只检验一个条件。
(3)在编写函数时,要进行反复的考查,并且自问:“我打算做哪些假定?”一旦确定了的假定,就要使用断言对假定进行检查。
(4)一般教科书都鼓励程序员们进行防错性的程序设计,但要记住这种编程风格会隐瞒错误。当进行防错性编程时,如果“不可能发生”的事情的确发生了,则要使用断言进行报警。
 

六、函数匹配

 

void print();
void print(double);
void print(int val);
void print(const string &);

    函数匹配的步骤:

    1)选择候选函数:其与被调函数同名,而且其名在调用点可见。

    2)选择可行函数:其形参数量和类型与本次调用提供的实参相对应。

    3)寻找最佳匹配:如果没有的话,报告错误。

    【Note】:

调用重载函数时要尽量避免强制类型转换。

 

七、函数指针

    函数指针指向的是函数而非对象。虽然不能返回函数,但是可以返回指向函数的指针。

 

#include <bits/stdc++.h>
using namespace std;
int add(int a,int b){return a+b;}
int multiply(int a,int b){return a*b;}
int subtract(int a,int b){return a-b;}
int divide(int a,int b){return b!=0 ? a/b : 0;}
bool L_M(const string &s1,const string &s2)
{
	return s1.size() > s2.size() ? true : false;
}
int main()
{
	auto pf = &L_M;//pf是指向函数的指针。
	cout << pf("ab","abc") << endl;
	typedef int(*p)(int a,int b);//使用typedef定义指向函数的指针。
	vector<p> vec{add,multiply,subtract,divide};
	for (auto f : vec)
	{
		cout<<f(2,3)<<endl;
	}
	return 0;
}

 

标签:const,函数,形参,int,void,C++,Primer,string
From: https://blog.51cto.com/u_6526235/7788141

相关文章

  • 全面理解C++中的类
    1.类的访问属性:public,protect,privateC++中类的成员变量和函数都带有三种属性中的一种,假如没有特别声明,那么就默认是私有的(除了构造函数)。public表示是公开的,对象可以直接调用的变量或者函数;protect表示是保护性的,只有本类和子类函数能够访问(注意只是访问,本类对象和子类对象都不可......
  • 迷失岛第4章 函数里面带int参数与按钮的结合 与一些其他东西的运用
      这个代码 里面带参数 与按钮结合可以触发一些内容 比如按下这个按键进行计算啥的 也算是一个新方法  ContainsKey就是查找里面有没有 移除指定元素 定义GameObject在Scene中是否处于活动状态 ......
  • C++移动和获取文件读写指针
    在读写文件时,有时希望直接跳到文件中的某处开始读写,这就需要先将文件的读写指针指向该处,然后再进行读写。ifstream类和fstream类有seekg成员函数,可以设置文件读指针的位置;ofstream类和fstream类有seekp成员函数,可以设置文件写指针的位置。所谓“位置”,就是指距离文件开头......
  • C++系列十:日常学习-范围库Ranges
    目录前言介绍举例:前言不错麽内容参考https://zh.cppreference.com/w/cpp/rangesChatjpt总结注意点:确保你的C++编译器支持C++20标准包含ranges头文件views的操作是惰性的,它们不会立即执行,而是在需要时计算。这意味着你可以构建复杂的管道,而不必担心性能问题。提供......
  • Python turtle.circle()函数
    turtle.circle()函数   定义:turtle.circle(radius,extent=None)   作用:根据半径radius绘制extent角度的弧形   参数:             radius:弧形半径                            当radius值为正数时,圆心在当前位置/小海龟左......
  • Day16 函数对象--函数嵌套调用--闭包函数
    1.Day15_复习1: 2.Day15_复习2: 3.Day15_复习3: 4.函数对象_可以赋值_可以当做函数参数传给另外一个函数: 5.函数对象_可以当做函数另外一个函数的返回值_可以当做容器类型的一个元素: 6.函数对象初步实现ATM流程: 7.函数对象应用案例优化: 8.函数的嵌套调用: 9.......
  • Oracle和达梦:相似度函数:UTL
    Oracle和达梦的:相似度函数:UTL函数:UTL_MATCH.edit_distance_similarityUTL_MATCH.edit_distance_similarity是Oracle数据库中的一个函数,用于计算两个字符串之间的相似度。它基于编辑距离算法,该算法用于衡量两个字符串之间的相似程度。编辑距离是通过计算将一个字符串转换为......
  • Clickhouse时间日期函数一文详解+代码展示
    转:https://blog.csdn.net/master_hunter/article/details/125762575一、时间函数和MySQL时间函数有些不同,但是时间函数的功能是一样的,这里把常用的时间函数给出,效果以实际代码运行结果为准: 1.取当前时间SELECT now()AStimeSELECT today()AStime获取当前时间戳SELECT ......
  • Go函数全景:从基础到高阶的深度探索
    在本篇文章中,我们深入探索了Go语言中的函数特性。从基础的函数定义到特殊函数类型,再到高阶函数的使用和函数调用的优化,每一个部分都揭示了Go的设计哲学和其对编程效率的追求。通过详细的代码示例和专业解析,读者不仅可以掌握函数的核心概念,还能了解如何在实践中有效利用这些特性来......
  • c++ OOP(2)
    目录运算符重载继承多态抽象基类运算符重载重新定义+-*/操作,对同类对象使用,以时间类Time为例子进行理解Time.hclassTime{private:inthour,minute;public:Time();Time(inth,intm);voidshow_time();Timeoperato......