欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

第7讲——函数初步

程序员文章站 2022-05-28 13:07:33
...

我们知道,C++自带了一个包含函数的大型库(标准ANSI库加上多个C++类),但这并不能满足我们的需求,我们需要编写自己的函数。但我们在编写函数时为了提高编程效率,可更深入地学习STL和BOOST C++提供的功能。

 

我们先学习一下库函数,它是已经定义和编译好的函数,同时可以使用标准库头文件提供其原型,因此只需正确地调用这种函数即可。例如,标准C库中有一个strlen()函数,相关的标准头文件cstring包含了strlen()和其他一些与字符串相关的函数的原型。这些预备工作使程序员能够在程序中随意使用strlen函数。

 

所以,创建自己的函数时,需要我们自行处理这3个方面——定义、提供原型和调用。

 

我们知道函数分为:有返回值的函数和没有返回值的函数。

对于有返回值的函数,C++对于返回值的类型有一定的限制:不能是数组,但可以是其他任何类型——整数、浮点数、指针、结构和对象。

有趣的是,虽然不能直接返回数组,但可以将数组作为结构或对象组成部分来返回。

下面我们来讨论函数是如何处理数组的。

若要设计一个对数组中元素求和的函数,那么这个函数需要知道对哪个数组进行累计,因此我们需要将数组名作为参数传递给它。为使函数通用,而不限于特定长度的数组,还需要传递数组长度。下面我们来看一看函数头及其其他部分:

int sum_arr(int arr[],int n)   //arr是数组名,n是数组长度

这看上去表示:方括号指出arr是一个数组,而方括号为空则表明,可以将任何长度的数组传递给该函数。

但实际情况并非如此:arr实际上并不是数组,而是一个指针!好消息是,在编写函数的其余部分时,可以将arr看作是数组。

下面我们来研究研究函数如何使用指针来处理数组:

我们知道,C++将数组名视为指针。之前我们学过C++将数组名解释为第一个元素的地址:cookies == &cookies[0] 。但是我们应该要知道的是:①数组声明使用数组名来标记存储位置;②对数组名使用sizeof将得到整个数组的长度(以字节为单位);③将地址运算符&用于数组名时,将返回整个数组的地址。

当我们在调用函数中如此调用数组时:int sum = sum_arr(cookies, ArSize); (cookies是数组名),根据C++规则,cookies是其第一个元素的地址,因此函数传递的是地址。而由于数组的元素的类型为int,因此cookies的类型必须是int指针,即int*。这表明,正确的函数头应该是这样的:

int sum_arr(int *arr,int n)    //arr是数组名

其中,用int *arr替换了int arr[] 。这证明两个函数头都是正确的,因为在C++中,当(且仅当)用于函数头或函数原型中,int *arr和int arr[]的含义才是相同的。它们都意味着arr是一个int指针。然而,数组表示法(int arr[])提醒用户,arr不仅指向int,还指向int数组的第一个int。

 

有一点需要记住,不同于传递常规变量(需要新建变量存储值),传递数组时,函数将使用原来的数组,这将可以节省复制整个数组所需的时间和内存。

 

下面,我们来讨论函数与结构。

相比于数组,涉及到函数时,结构变量的行为更接近于基本的单值变量。也就是说,与数组不同,结构将其数据组合成单个实体或数据对象,该实体被视为一个整体。

那么,我们给出结构在函数中的特性:①可以按值传递结构,就像普通变量那样,同时,函数将使用结构的副本;②函数可以返回结构,与数组名就是第一个元素的地址不同的是,结构名只是结构的名称,要获得结构的地址必须使用地址运算符&。

使用结构编程时,最直接的方式是像处理基本类型那样处理结构。也就是说,将结构作为参数传递,并在需要时将结构用作返回值使用。由于按值传递结构可能因结构过大而耗费大量内存,所以许多C程序员倾向于传递结构的地址,然后使用指针来访问结构的内容,然而C++提供了第三种选择——按引用传递。

【按值传递】

struct travel_time{
	int hours, mins;
};

travel_time sum(travel_time t1, travel_time t2)	//返回类型为结构类型 
{
	...
}

int main()
{
	travel_time day1 = {5, 45};
	travel_time day2 = {4, 55};
	travel_time trip = sum(day1, day2);		//结构变量作为参数 
}

在这里,结构体类型travel_time就像是一个标准的类型名,可被用来声明变量、函数的返回类型和函数的参数类型。

【按地址传递】

与传值操作不同的是,这能让函数对原始结构进行操作,而不是结构副本。

传递结构的地址而不是整个结构以节省时间和空间,此时需要使用指向结构的指针。

需要注意的是,如果我们希望在函数中不修改结构,我们应使用const修饰符。

 

关于函数和string对象,待日后学完string再来填坑。(p235)

 

突然看到书上有函数指针,我靠,别逗我笑,这种低级书应该只是形式上地描写一下。我们也先大概谈谈吧。

看了一下,书上讲的篇幅还挺多,我现在没时间扯这个了,以后需要再看。(p241)

 

温故而知新

【使用函数的3个步骤】

定义函数;提供原型;调用函数。

【编写一个接受3个参数的函数:指向数组区间中第一个元素的指针、指向数组区间最后一个元素后面的指针,以及一个int值,并将数组中每一个元素都设置为该int值】

void set_array(int *begin, int *end, int value)
{
	while(begin != end){
		*begin = value;
		begin++;
	}
} 

//注:在函数内部移动指针,离开函数后,指针恢复到初始位置(应该是在函数内部创建了一个指针副本)。因此无需在函数内部新创建一个指针。

【为什么不对类型为基本类型的函数参数使用const限定符】

答:因为函数在调用参数时,使用的是一个副本,而不是原来的数,因此不会修改作为实参的基本类型的值。而指针不同,指针为函数参数时,可以通过修改直着,来修改指针所指向的值。

【C++程序可以使用哪3种C-风格字符串格式】

答:字符串可以被储存在char数组中,可以使用带双引号的字符串来表示(比如"abc",但这种无法被修改),也可以用指向字符串第一个字符的指针来表示。

【表达式*"pizza"的含义是什么?"taco"[2]呢】

答:

*"pizza"的含义是:"pizza"是一个常量字符串,其名字表示为指向其地址的指针(类型为char*),对这个指针解除运算,是字符串的第一个字符——即p。*"pizze"的结果是:p

"taco"[2]的含义是:原理同上,这个常量字符串的第三个字符——是c。

以上答案存疑。

参考答案给的是:C++将字符串解释为指其第一个元素的地址,即p和t的地址,*给出第一个元素的值,[2]给第三个元素的值,所以分别是p和c。

【C++允许按值传递结构,也允许传递结构的地址。如果glitz是一个结构变量,如何按值传递他它?如何传递他的地址?这两种方法有何利弊?】

答:

按值传递则是传递他的类型,然后glitz作为参数进行传递。按地址传递则是参数使用结构指针。

按值传递的好处是不会修改原结构变量,按地址传递的好处正好是可以在函数内修改原结构变量。

假如结构类型是abc,则声明结构是abc glitz;

按值传递函数原型假如为:void mmm(abc);

按地址传递函数原型假如为:void mmm(abc*);

glitz作为参数时,按值是glitz,按地址则为&glitz。

补充:按值传递将自动保护原始数据,但这是以时间和内存为代价的(因为要复制副本),按地址传递可节省内存和时间,但不能保护原始数据,解决办法是使用const限定符。

【假设有如下结构声明:struct applicant  {char name[30];  int credit_ratings[3];};

a。编写一个函数,它将applicant结构作为参数,并显示该结构的内容。

b。编写一个函数,他将applicant结构的地址作为参数,并显示该参数指向的结构的内容。】

void show_1(applicant m)
{
	cout<<m.name<<endl;
	for(int i=0;i<3;i++)
		cout<<m.credit_ratings[i]<<endl;
}

void show_2(applicant *m)
{
	cout<<m->name<<endl;
	for(int i=0;i<3;i++)
		cout<<(*m).credit_ratings[i]<<endl;
}