第7讲——函数初步

我们知道,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;
}

 

posted @ 2017-08-16 21:56  GGBeng  阅读(216)  评论(0编辑  收藏  举报