Chapter 7函数——C++的编程模块

本章需要重点掌握的内容:

  • 设计函数
  • 使用const指针参数
  • 调用自身的函数
  • 指向函数的指针

7.1 函数的基本知识

要使用C++函数,必须完成如下工作:

  • 提供函数定义
  • 提供函数原型
  • 调用函数

7.1.1 定义函数

无返回值的函数通用格式如下:</font>
void functionName(parameterList)
{
	statement(s);
}

有返回值的函数:

typeName functionName(parameterList):
{
	statement(s);
	return vlaue; // value is type cast to type typeName
}

7.1.2 函数原型和函数调用

  函数原型通常隐藏在inclue文件中。

  • 为什么需要原型
    原型提供了函数的参数类型和数量以及返回值的类型(如果有的话)告诉编译器。
  • 原型的语法
    函数原型是一条语句,必须以分号结束。
    函数原型不要求提供变量名,有函数列表就足够了。
  • 原型的功能
    原型确保以下几点:
  1. 编译器正确处理函数返回值
  2. 编译器检查使用参数数目是否正确
  3. 编译器检查使用参数类型是否正确如果不正确,则转换为正确的类型(如果可以的话)

7.2 函数参数和按值传递

参数(argument)表示实参,参量(parameter)表示形参,参数传递将参数赋给参量。

7.2.1 多个参数

函数的多个参数用逗号隔开。

7.3 函数和数组

7.3.1 函数如何使用指针来处理数组

在大多数情况下,cookies == &cookies[0],&cookies将返回整个数组的地址,通常是一个大的内存块。
在函数声明中,int * arrint arr[]是等效的。

7.3.2 将数组作为参数意味着什么

数组名与指针对应:

  • 将数组地址作为参数可以节省复制整个数组所需的时间和内存
  • 使用原始数据增加了破坏数据的风险。C++使用const限定符解决该问题。

注:指针本身并没有指出数组的长度,将数组类型和元素数量告诉数组处理函数时,请通过两个不同的参数来传递他们;而不要试图用方括号表示法来传递数组长度。
void fillArray(int arr[size]) //NO -- bad prototype

7.3.3 更多数组函数示例

构思程序时将存储属性与操作结合起来,便是朝oop思想迈进了重要的一步。

  1. 填充数组
    在数组填满之前停止读取数据,在函数中建立这种特性。
int fill_array(double arr[], int limit)
{
	using namespace std;
	double temp;
	int i;
	for(i = 0; i < limit; i++)
	{
		cout << "Enter value #" << (i + 1) << ": ";
		cin >> temp;
		if(!cin)
		{
			cin.clear();
			while(cin.get() != '\n')
				coutinue;
			cout << "Bad input; input process terminated.\n";
			break;
		}
		else if(temp < 0)
			break;
		arr[i] = temp;
	}
	return i;
}
  1. 显示数组及用const保护数组
    创建显示数组内容的函数很简单,需要确保显示函数不修改原始数组。
void show_array(const double ar[], int n)
{
	using namespace std;
	for(int i = 0; i < n; i++)
	{
		cout << "Property #" << i << " : $";
		cout << ar[i] << endl;
	}
}
  1. 修改数组
void revalue(double r, double ar[], int n)
{
	for(int i = 0; i < n; i++)
		ar[i] *= r;
}
  1. 数组处理函数常用的编写方式
    修改数组:
void f_modify(double ar[], int n)

不修改数组:

void f_no_change(const double ar[], int n);

这两种编写方式不能用sizeof来获悉原始数组的长度。

7.3.4 使用数组区间的函数

另一种数组参数传递的方法,指定元素区间,通过两个指针来完成。
注:使用const时,在函数中定义指针也需要const.

7.3.5 指针和const

两种不同的方式将const关键字用于指针。

  • 让指针指向一个常量对象,这样可以防止使用该指针来修改所指向的值

首先,声明一个指向常量的指针pt:
int age = 39;
const int * pt = &age;
该声明指出,pt指向一个const int(这里为39),因此不能使用pt来修改这个值。*pt的值为const,不能被修改:
*pt += 1;// INVALID beacuse pt point to a const int
cin >> pt;// INVALID for the same reason

  • 将指针本身声明为常量(const),这样可以防止改变指针指向的位置
    两种可能:将const变量的地址赋给指向const的指针,将const的地址赋给常规指针。
    第一种操作可行,第二种不可行。
    const float g_earth = 9.80;
    const float * pe = &g_earth; // VALID

const float g_moon = 1.63;
float * pm = &g_moon; // INVALID
如果上面的操作可以,则可以通过pm修改g_moon的值,这使得const的状态很荒谬。因此禁止该情况。

尽可能使用const
将指针参数声明为指向常量数据的指针有两条理由:

  • 这样可以避免由于无意修改数据而导致的编程错误;
  • 使用const是的函数能够处理const和非const实参,否则只能接受非const数据。
    如果条件允许,则应该将指针形参声明为指向const的指针。

7.4 函数和二维数组

二维数组作为参数的函数声明和调用:

  • 使用指针
    int sum_ar2((*ar2)[4], int size)
  • 使用数组
    int sum_ar2(int ar2[][4], int size)

要想得到二维数组的数据:
* ((* ar2 + r) + c)
得到r行c列的数据。

7.5 函数和C-风格字符串

将字符串作为参数时意味着传递的是地址,可以使用const来禁止对字符串参数进行修改。

7.5.1 将C-风格字符串作为参数的函数

处理字符串中字符的标准方式:

while(*str)
{
	statements;
	str++;
}

7.5.2 返回C-风格字符串的函数

返回的是一个地址,接受函数也应该是一个地址,使用动态存储记得使用delete释放空间。
char * buildstr(char ch, int n);
调用:
char * pt = bulid('+',20);

完成后记得 delete[]pt

7.6 函数和结构

结构名只是结构的名称,数组名是第一个数组元素的地址。结构必须使用&地址运算符。

7.6.1 传递和返回结构

当结构比较小时,按值传递结构最合理。

7.6.2 传递结构的地址

传递结构的地址可以节省时间和空间。
修改:

  • 调用函数时,将结构的地址(&pplace)而不是结构本身传递;
  • 形参声明为指向结构的指针,即polar * 的类型,在不修改结构时使用const修饰符
  • 形参是指针,因此应接成员运算符->而不是.;

7.7 函数和string对象

string对象与结构的用途更相似。
string数组,string对象读行getline(cin,str);

7.8 函数与array对象

类对象是基于结构的。在传递给函数时有两种方式:

  • 按值传递给函数,这种情况下,函数处理的时原始对象的副本;
  • 传递指向对象的指针,函数可以操作原始对象。

7.9 递归

函数自己调用自己,该功能成为递归。(C++不允许main()函数调用自己)。

7.9.1 包含一个递归调用的递归

void recurs(argumentlist)
{
	statements1
	if (test)
		recurs(arguments)
	statements2
}

该程序recurs进行5次递归调用,则第一个statements1顺序执行5次,第二个statements2将与函数相反的顺序执行5次。

7.9.2 包含多个递归调用的递归

样例程序:

// ruler.cpp -- using recursion to subdivide a ruler
#include<iostream>
const int Len = 66;
const int Divs = 6;
void subdivide(char ar[], int low, int high, int level);

int main()
{
    char ruler[Len];
    int i;
    for(i = 1; i < Len - 2; i++)
        ruler[i] = ' ';
    ruler[Len - 1] = '\0';
    int max = Len - 2;
    int min = 0;
    ruler[min] = ruler[max] = '|';
    std::cout << ruler << std::endl;
    for(i = 1; i <= Divs; i++)
    {
        subdivide(ruler,min,max, i);
        std::cout << ruler << std::endl;
        for (int j = i; j < Len - 2; j++)
            ruler[j] = ' ';
    }
    return 0;
}

void subdivide(char ar[], int low, int high, int level)
{
    if (level == 0)
    return;
    int mid = (high + low) / 2;
    ar[mid] = '|';
    subdivide(ar, low, mid, level - 1);
    subdivide(ar, mid, high, level - 1);
}

该程序使用变量level来控制递归层,函数调用自身时,将level-1,当level为0时,函数将不在调用自己。subdivide调用自己两次,一次针对左半部分,一次针对右半部分。注意,调用次数将呈几何级数增长.当递归层次较少时,该方法很适合。

7.10 函数指针

与数据项类似,函数也有地址。函数的地址是存储其机器语言代码的内存的开始地址。

7.10.1 函数指针的基础知识

将程序员要使用的算法函数的地址传给estimate(),为此,必须完成以下工作:

  • 获取函数的地址;
    使用函数名,后面不跟参数就是该函数的地址。
    process(think); // passes address of think()
    thought(think()); // passes the return value of think
  • 声明一个函数指针;
    正确的声明如下:
double pam(int);
double (*pt) (int);// () is must
pt = pam;

double *pf (int)意味着声明了一个函数,返回一个double类型的指针

  • 使用函数指针来调用函数。
    使用指针函数是,将他看成函数名即可。

7.10.2 深入讨论函数指针

使用auto来简化简化初始化,注:auto只能用于单值初始化

7.10.3 使用typedef进行简化

声明函数指针类型的别名

typedef const double *(*p_fun)(const double * ,int);
p_fun p1 = f1;

7.11 复习题

  1. 使用函数的3个步骤是什么?
  • 函数声明(提供原型)
  • 函数定义
  • 函数调用
  1. 请创建与下面的描述匹配的函数原型。
    a. igor没有参数,且没有返回值。
    void igor();

    b. tofu()接受一个int参数,并返回一个float。
    float tofu(int);

    c. mpg()接受两个double参数,并返回一个double参数。
    double mpg(double miles, double gallons);

    d. summation()将long数组名和数组长度作为参数,并返回一个long值。
    long summation(long[], int);

    e. doctor()接受一个字符串参数(不能修改该字符串),并返回一个double值。double doctor(const std::string s); //wrong

    double doctor(const char * str);
    f. ofcourse()将boss结构作为参数,不返回值。
    void ofcourse(boss b);

    g. plot将map结构的指针作为参数,,并返回一个字符串。
    string plot(map *pt); //wrong, can't return string, can return point

    char * plot(map *pmap);

  2. 编写一个接受三个参数的函数,int数组名、数组长度和一个int值,并将数组的所有元素都设置为该int值。

void setar(int ar[], int len, int n)
	for(int i = 9; i < len; i++)
		ar[i] = n;
  1. 编写一个接受三个参数的函数,指向数组区间第一个元素的指针、指向数组区间最后一个元素的指针和一个int值,并将数组的所有元素都设置为该int值。
void setar(int * begin, int * end, int n)
	int * pt;
	pt = begin;
	while(pt != end)
	{
		*pt = n;
		pt++;
	}
  1. 编写将double数组名和数组长度作为参数,并返回该数组中最大值的函数。该函数不应该修改数组的内容。
double find_max(const double ar[], int len)
{
	double max = ar[0];
	for(int i = 0; i < len; i++)
		if(max < ar[i])
			max = ar[i];
	return max; // 
}
  1. 为什么不对类型为基本类型的函数参数使用const限定符?
    函数传递基本类型的参数按值传递使用的是参数副本,不会修改原始数据,因此不需要const限定符,使用指针怕对原始数据产生影响,因此使用const限定符。
  2. C++程序可以使用哪3中C-风格字符串格式?
  • 字符数组
  • 字符串常量
  • string对象指向字符串第一个字符的指针
  1. 编写一个函数,其原型如下:
    int replace(char *str, char c1, char c2);
    该函数将字符串中所有的c1都替换为c2,并返回替换次数;
int replace(char *str, char c1, char c2)
{
	int count = 0;
	while(*str != '\0')
	{
		if(*str == c1)
		{
			*str = c2;
			count++;
		}
		str++;
	}
	return count;
}
  1. 表达式*"pizza"的含义是什么?"taco"[2]呢?

    *"pizza"代表常字符串"pizza"的地址字符串"pizza"的字符p的地址"taco"[2]存在一个字符串数组,有两个元素,每个元素都是常字符串"taco"指的是字符串数组的第三个字符,即c
  2. C++允许按值传递结构,也允许传递该结构的地址。如果glitz是一个结构变量,如何按值传递它?如何传递它的地址?这两种方法有何利弊?
    函数会创建glitz的一个副本,使用该函数的副本,不会对glitz的原始数据产生影响,但会浪费时间和存储空间;使用地址式函数直接使用指针指向该结构,利用指针直接访问该结构,该方法可以节约空间和时间,但可能会改变原始数据。
  3. 函数judge()的返回类型为int,它将这样一个函数的地址作为参数:将const char指针作为参数,并返回一个int值。请编写judge()函数的原型。
int judge((*int)(const char*));//wrong
int judge(int(*pt)(const char *));
  1. 假设有如下结构声明:
struct applicant{
	char name[30];
	int credit_ratings[3];
}

a. 编写一个函数,它将application结构作为参数,并显示该结构的内容。
b. 编写一个函数,它将application结的地址构作为参数,并显示该结构的内容。
a.

void display(const applicant application)
{
	cout << "Name: " << application.name << endl;
	for(int i = 0; i < 3; i++)
		cout << "credit_rate #" << i+1 << ": " << application.credit_ratings << endl;
}

b.

void display(const applicant * application)
{
	cout << "Name: " << application->name << endl;
	for(int i = 0; i < 3; i++)
		cout << "credit_rate #" << i+1 << ": " << application->credit_ratings << endl;
}
  1. 假设函数f1()和f2()的原型如下:
void f1(applicant * a);
const char * f2(const applicant * a1, const applicant * a2);

请将p1和p2分别声明为指向f1和f2的指针;将ap声明为一个数组,它包含5个类型与p1相同的指针;将pa声明为一个指针,它指向的数组包含10个类型与p2相同的指针。使用typedef来完成这项工作。

typedef void (*pf1)(applicant *a);//typedef void (*pf1)(applicant *)
pf1 p1;
p1 = f1; // pf1 p1 = f1;
typedef const char * (*pf2)(applicant *, applicant *);
pf2 p2;
p2 = f2;// pf2 p2 = f2;

pf1 ap[5];
pf2 ap2[10];
auto pa = &ap2
void *(*(*pf2))[10])(applicant *a1, applicant *a2) = &ap2;
//pf2 (*pa)[10]

posted @ 2021-12-17 15:21  Fight!GO  阅读(110)  评论(0编辑  收藏  举报