C++学习笔记——函数——C++的编程模块

六. 函数——C++的编程模块

6.1  复习函数的基本知识

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

        · 提供函数定义;

        · 提供函数原型;

        · 调用函数。

calling.cpp

#include <iostream>
using namespace std;

//函数原型
void simple();

int main() {
    cout << "main() will call the simple() function:\n";

    //函数调用
    simple();
    cout << "main() is finished with the simple() function.\n";

    return 0;
}

//函数定义
void simple()
{
    cout << "I'm but a simple function.\n";
}

6.1.1 定义函数、

        可以将函数分为两类:没有返回值的函数和有返回值的函数。没有返回值的函数被称为void函数,其通用格式如下:

void functionName(parameterList)

{

        statement(s);

        return;

}

        其中,parameterList指定了传递给函数的参数类型和数量,可选的返回语句标记了函数的结尾,否则函数将在花括号处结束。

        有返回值的函数将生成一个值,并将它返回给调用函数,其通用格式如下:

typeName functionName(parameterList)

{

        statements;

        return value;

}

        对于有返回值的函数,必须使用返回语句,以便将值返回给调用函数。值本身可以是常量、变量,也可以是表达式,只是其结果的类型必须为typeName类型或可以被转换为ypeName(例如如果声明的返可类型为double,而函数返回一个int表达式则该int值将被强制转换为double类型)。然后,函数将最终的值返回给调用函数。C++对于返回值的类型有一定的限制,不能是数组,但可以是其他任何类型——整数、浮点数、指针,甚至可以是结构和对象。虽然:C++函数不能直接返回数组,但可以将数组作为结构或对象组成部分来返回。

6.1.2 函数原型和函数调用

1. 为什么需要原型

        原型描述了函数到编译器的接口,也就是说,它将函数返回值的类型(如果有)以及参数的类型和数量告诉编译器。

2. 原型的语法

        函数原型是一条语句,因此必须以分号结束。函数原型不要求提供变量名,有类型列表就够了。通常,在原型的参数列表中,可以包括变量名,也可以不包括。原型中的变量名相当于占位符,因此不必与函数定义中的变量名相同。

3. 原型的功能

        · 确保编译器正确处理函数返回值;

        · 编译器检查使用的参数数目是否正确;

        · 编译器检查使用的参数类型是否正确。

        通常,原型自动将被传递的参数强制转换为期望的类型。仅当有意义时,原型化才会导致类型转换,例如原型不会将整数转换为结构或指针。

        在编译阶段进行的原型化被称为静态类型检查(static type checking),它可以捕获许多在运行阶段难以捕获的错误。

6.2 函数参数和按值传递

        用于接收传递值的变量被称为形参,传递给函数的值被称为实参,C++标准使用参量(argument)来表示实参,使用参数(parameter)来表示形参,因此参数传递将参量赋给参数。

        在函数中声明的变量(包括参数)是该函数私有的,在函数被调用时,计算机将为这些变量分配内存;在函数结束时,计算机将释放这些变量使用的内存,这样的变量被称为局部变量。

6.2.1 多个参数

        函数可以有多个参数,在调用函数时,只需用逗号将这些参数分开即可。

twoarg.cpp

#include <iostream>
using namespace std;

void n_chars(char, int);

int main() {
    int times;
    char ch;

    cout << "Enter a character: ";
    cin >> ch;
    while (ch != 'q')
    {
        cout << "Enter an integer: ";
        cin >> times;
        n_chars(ch, times);
        cout << "\nEnter another character or press the"
            "q-key to quit: ";
        cin >> ch;
    }
    cout << "The value of times is " << times << ".\n";
    cout << "Bye\n";

    return 0;
}

void n_chars(char c, int n)
{
    while (n-- > 0)
        cout << c;
}

6.3 函数和数组

arrfun1.cpp

#include <iostream>

const int Arsize = 8;

int sum_arr(int arr[], int n);

int main() {
	using namespace std;
	int cookies[Arsize] = { 1,2,4,8,16,32,64,128 };
	int sum = sum_arr(cookies, Arsize);
	cout << "Total cookies eaten: " << sum << "\n";
	return 0;
}

int sum_arr(int arr[], int n) {
	int total = 0;
	for (int i = 0; i < n; i++)
		total = total + arr[i];
	return total;

}

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

        在大多数情况下,C++和C语言一样,也将数组名视为指针,C++将数组名解释为其第一个元素的地址。下面两个式子有助于理解:

arr[i] == *(arr + i);

&arr[i] == arr + i;

        再arrfun1.cpp中,实际上没有将数组内容传递给函数,而是将数组地址、包含的元素种类以及元素数目提交给函数,传递常规变量时,函数将使用该变量的拷贝,但传递数组时,函数将使用原来的数组。

arrfun2.cpp

#include <iostream>

const int Arsize = 8;

int sum_arr(int arr[], int n);

int main() {
	int cookies[Arsize] = { 1,2,4,8,16,32,64,128 };
	std::cout << cookies << " = array address, ";
	std::cout << sizeof cookies << " = sizeof cookies\n";
	int sum = sum_arr(cookies, Arsize);
	std::cout << "Total cookies eaten: " << sum << std::endl;
	sum = sum_arr(cookies, 3);
	std::cout << "First three eaters ate " << sum << " cookies.\n";
	sum = sum_arr(cookies + 4, 4);
	std::cout << "Last four eaters ate " << sum << " cookies.\n";

	return 0;
}

int sum_arr(int arr[], int n) {
	int total = 0;
	std::cout << arr << " = arr, ";
	std::cout << sizeof arr << " = sizeof arr\n";
	for (int i = 0; i < n; i++)
		total = total + arr[i];
	return total;
}

        为将数组类型和元素数量告诉数组处理函数,请通过两个不同的参数来传递它们:

        void fillArray(int arr[], int size);

        而不要试图通过方括号表示法来传递数组长度:

        void fillArray(int arrsize[size]);

6.3.2 一个简单房地产案例

arrfun3.cpp

#include <iostream>

const int Max = 5;

int fill_array(double ar[], int n);
void show_array(const double ar[], int n);
void revalue(double r, double ar[], int n);

int main() {
	using namespace std;
	double properties[Max];

	int size = fill_array(properties, Max);
	show_array(properties, size);
	if (size > 0)
	{
		cout << "Enter revalution factor: ";
		double factor;
		while (!(cin >> factor))
		{
			cin.clear();
			while (cin.get() != '\n')
				continue;
			cout << "Bad input; Please enter a number: ";
		}
		revalue(factor, properties, size);
		show_array(properties, size);
	}
	cout << "Done.\n";
	cin.get();
	cin.get();

	return 0;
}

int fill_array(double ar[], 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')
				continue;
			cout << "Bad input; intput process terminated.\n";
			break;
		}
		else if (temp < 0)
			break;
		ar[i] = temp;
	}
	return i;
}

void show_array(const double ar[], int n) {
	using namespace std;
	for (int i = 0; i < n; i++) {
		cout << "Property #" << (i + 1) << ": $";
		cout << ar[i] << endl;
	}
}

void revalue(double r, double ar[], int n) {
	for (int i = 0; i < n; i++)
		ar[i] *= r;
}

int fill_array(double ar[], int limit);

        该函数接受两个参数,一个是数组名,另一个指定了要读取的最大元素数;该函数返回实际读取的元素数。

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

        为防止函数无意中修改数组的内容,可在声明形参时使用关键字const,C++将声明const double ar[]解释为const double* ar,这意味着ar指向的是常量数据,不能在show_array()函数中使用ar来修改这些数据,即show_array()将数组视为只读数据。

6.3.3 指针和const

        可以用两种不同的方式将const关键字用于指针。第一种方法是让指针指向一个常量对象,这样可以防止使用该指针来修改所指向的值,第二种方法是将指针本身声明为常量,这样可以防止使用该指针来修改所指向的值,第二种方法是将指针本身声明为常量,这样可以防止改变指针所指向的位置。

        指向常量的指针:

        int age = 39;

        const int* pt = &age;

        声明指针为常量:

        int sloth = 3;

        const int * ps = &sloth;

        int* const finger = &sloth;

        这种声明格式使得finger只能指向sloth,但允许finger来修改sloth的值。

        通常,将指针作为函数参数来传递时,可以使用指向const的指针来保护数据。

6.4 函数和二维数组

        int data[3][4] = {{1, 2, 3,4}, {9, 8, 7, 6}, {2, 4, 6, 8}};

        int total = sum(data,3);

        Data是一个数组名,该数组有3个元素。第一个元素本身是一个数组,有4个int值组成,因此data的类型是指向由四个int组成的数组的指针,原型如下:

        int sum(int (*ar2)[4], int size);

        或者:

        int sum(int ar2[][4], int size);

6.5 函数和C风格字符串

        假设要将字符串作为参数传递给函数,则表示字符串的方式有三种:

        · char数组;

        · 用引号括起的字符串常量(也称字符串字面值);

        · 被设置为字符串的地址的char指针。

        可以说是将字符串作为参数来传递,但实际传递的是字符串第一个字符的地址。这意味着字符串函数原型应将其表示字符串的形参声明为char*类型。
        C-风格字符串与常规 char 数组之间的一个重要区别是,字符串有内置的结束字符。这意味着不必将字符串长度作为参数传递给函数,而函数可以使用循环依次检查字符串中的每个字符,直到遇到结尾的空值字符为止。

strgfun.cpp

#include <iostream>

unsigned int c_in_str(const char* str, char ch);

int main() {
	using namespace std;
	char mmm[15] = "minimum";

	const char* wail = "ululate";
	unsigned int ms = c_in_str(mmm, 'm');
	unsigned int us = c_in_str(wail, 'u');
	cout << ms << " m characters in " << mmm << endl;
	cout << us << " u characters in " << wail << endl;
	
	return 0;
}

unsigned int c_in_str(const char* str, char ch) {
	unsigned int count = 0;

	while (*str)
	{
		if (*str == ch)
			count++;
		str++;
	}
	return count;
}

返回C风格字符串的函数:

strback.cpp

#include <iostream>

char* buildstr(char c, int n);

int main() {
	using namespace std;
	int times;
	char ch;
	cout << "Enter a character: ";
	cin >> ch;
	cout << "Enter a integer: ";
	cin >> times;
	char* ps = buildstr(ch, times);
	cout << ps << endl;	//使用cout << 来输出一个指针时,它将打印指针所指向的 C 风格字符串
	delete[] ps;
	ps = buildstr('+', 20);
	cout << ps << "-DONE-" << ps << endl;
	delete[] ps;

	return 0;
}

char* buildstr(char c, int n) {
	char* pstr = new char[n + 1];
	pstr[n] = '\0';
	while (n-- > 0)
		pstr[n] = c;
	return pstr;
}

6.6 函数和结构

6.6.1 传递和返回结构

travel.cpp

#include <iostream>

struct travel_time {
	int hours;
	int mins;
};

const int Mins_per_hr = 60;

travel_time sum(travel_time t1, travel_time t2);
void show_time(travel_time t);

int main() {
	using namespace std;
	travel_time day1 = { 5,45 };
	travel_time day2 = { 4,55 };

	travel_time trip = sum(day1, day2);
	cout << "Two day total: ";
	show_time(trip);

	travel_time day3 = { 4,32 };
	cout << "Three day total: ";
	show_time(sum(trip, day3));

	return 0;
}

travel_time sum(travel_time t1, travel_time t2) {
	travel_time total;
	total.mins = (t1.mins + t2.mins) % Mins_per_hr;
	total.hours = t1.hours + t2.hours+(t1.mins + t2.mins) / Mins_per_hr;
	return total;
}

void show_time(travel_time t) {
	using namespace std;
	cout << t.hours << " hours, "
		<< t.mins << " minutes\n";
}

atrctfun.cpp

#include <iostream>
#include <cmath>

struct polar {
	double distance;
	double angle;
};

struct rect {
	double x;
	double y;
};

polar rect_to_polar(rect xypos);
void show_polar(polar dapos);

int main() {
	using namespace std;
	rect rplace;
	polar pplace;
	cout << "Enter the x and y values: ";
	while (cin >> rplace.x >> rplace.y) {
		pplace = rect_to_polar(rplace);
		show_polar(pplace);
		cout << "Next two numbers (q to quit): ";
	}
	cout << "Done.\n";

	return 0;
}

polar rect_to_polar(rect xypos) {
	using namespace std;
	polar answer;

	answer.distance =
		sqrt(xypos.x * xypos.x + xypos.y * xypos.y);
	answer.angle = atan2(xypos.y, xypos.x);
	return answer;
}

void show_polar(polar dapos) {
	using namespace std;
	const double Rad_to_deg = 57.29577951;

	cout << "distance = " << dapos.distance;
	cout << ", angle = " << dapos.angle* Rad_to_deg;
	cout << " degrees\n";
}

        cin 是 istream类的一个对象。抽取运算符(>>)被设计成使得 cin>>rplacc.x 也是一个istream
对象。类运算符是使用函数实现的。使用 cin>>rplace.x 时,程序将调用一个函数,该函数返回一个istream值将抽取运算符用于 cin>>rplace.x对象(就像cin>>rplacex>>rplacey这样), 也将获得一个istream对象。因此,整个 while 循环的测试表达式的最终结果为 cin,而cin 被用于测试表达式
中时,将根据输入是否成功,被转换为 bool值 true 或false, 例如,在上述程序中的循环中,cin 期望用户输入两个数字,如果用户输入了 q(前面的输出示例就是这样做的),cin>>将知道 不是数字,从而将q留在输入队列中,并返回一个将被转换为 fasle 的值,导致循环结束。

6.6.2 传递结构的地址

        重新编写show_polar()函数,需要修改三个地方:

        · 调用函数时,将结构的地址(&pplace)而不是结构本身传递给它;

        · 将形参声明为指向polar的指针,即polar*类型,由于函数不应该修改结构,因此使用了从const修饰符;

        · 由于形参时指针而不是结构,因此应使用间接成员运算符(->),而不是成员运算符(句点)。

strctptr.cpp

#include <iostream>
#include <cmath>

struct polar {
	double distance;
	double angle;
};

struct rect {
	double x;
	double y;
};

void rect_to_polar(const rect* pxy, polar* pda);
void show_polar(const polar* pda);

int main() {
	using namespace std;
	rect rplace;
	polar pplace;
	cout << "Enter the x and y values: ";
	while (cin >> rplace.x >> rplace.y) {
		rect_to_polar(&rplace,&pplace);
		show_polar(&pplace);
		cout << "Next two numbers (q to quit): ";
	}
	cout << "Done.\n";

	return 0;
}

void rect_to_polar(const rect* pxy, polar* pda) {
	using namespace std;
	pda->distance =
		sqrt(pxy->x * pxy->x + pxy->y * pxy->y);
	pda->angle = atan2(pxy->y, pxy->x);
}

void show_polar(const polar* pda) {
	using namespace std;
	const double Rad_to_deg = 57.29577951;

	cout << "distance = " << pda->distance;
	cout << ", angle = " << pda->angle* Rad_to_deg;
	cout << " degrees\n";
}

6.7 函数和string对象

topfive.cpp

#include <iostream>
#include <string>

using namespace std;

const int SIZE = 5;

void display(const string sa[], int n);

int main() {
	string list[SIZE];
	cout << "Enter your " << SIZE << " favorite astronomical sights:\n";
	for (int i = 0; i < SIZE; i++) {
		cout << i + 1 << ": ";
		//getline(cin, list[i]);
		cin >> list[i];
	}

	cout << "Your list:\n";
	display(list, SIZE);

	return 0;
}

void display(const string sa[], int n) {
	for (int i = 0; i < n; i++)
		cout << i + 1 << ": " << sa[i] << endl;
}

6.8 函数与array对象

        在C++中,类对象是基于结构的,因此结构编程方面的有些考虑因素也适用于类。例如,可按值将对传递给函数,在这种情况下,函数处理的是原始对象的副本。另外,也可传递指向对象的指针,这让函能够操作原始对象。下面来看一个使用C++模板类array的例子。

arrobj.cpp

#include <iostream>
#include <string>
#include <array>

const int SEASONS = 4;
const std::array<std::string, SEASONS> Snames = {
	"Spring", "Summer", "Fall", "Winter"
};

void fill(std::array<double, SEASONS>* pa);
void show(std::array<double, SEASONS> da);

int main() {
	std::array<double, SEASONS> expenses;
	fill(&expenses);
	show(expenses);

	return 0;
}

void fill(std::array<double, SEASONS>* pa) {
	using namespace std;
	for (int i = 0; i < SEASONS; i++) {
		cout << "Enter " << Snames[i] << " expenses: ";
		cin >> (*pa)[i];
	}
}

void show(std::array<double, SEASONS> da) {
	using namespace std;
	double total = 0.0;
	cout << "\nEXPENSES\n";
	for (int i = 0; i < SEASONS; i++) {
		cout << Snames[i] << ": $" << da[i] << endl;
		total += da[i];
	}
	cout << "Total Expenses: $" << total << endl;
}

6.9 递归

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

recur.cpp

#include <iostream>

void countdown(int n);

int main() {
	countdown(4);

	return 0;
}

void countdown(int n) {
	using namespace std;
	cout << "Counting down ... " << n << endl;
	if (n > 0)
		countdown(n - 1);
	cout << n << ": Kaboom!\n";
}

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

ruler.cpp

#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 = 0; 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 = 1; 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);
}

6.10 函数指针

1. 获取函数的地址

        获取函数的地址很简单:只要使用函数名(后面不跟参数)即可。也就是说,如果 think()是一个函数,则 think 就是该函数的地址。要将函数作为参数进行传递,必须传递函数名。--定要区分传递的是函数的地址还是函数的返回值。

2. 声明函数指针

        声明指向某种数据类型的指针时,必须指定指针指向的类型。同样,声明指向函数的指针时,也必须指定指针指向的函数类型。这意味着声明应指定函数的返回类型以及函数的参数列表。也就是说声明应像函数原型那样指出有关函数的信息。

        通常,要声明指向特定类型的函数的指针,可以首先编写这种函数的原型,然后用(*pf)替换函数名。这样pf就是这类函数的指针。

double (*pf) (int);        //pf是一个指向函数的指针

double* pf(int);        //pf()是一个返回指针的函数

3. 使用指针来调用函数

 将(*pf)看作函数名即可。

fun_ptr.cpp

#include <iostream>

double betsy(int);
double pam(int);
void estimate(int lines, double (*pf)(int));

int main() {
	using namespace std;
	int code;
	cout << "How many lines of code do you need?";
	cin >> code;
	cout << "Here's Betsy's estimate:\n";
	estimate(code, betsy);
	cout << "Here's Pam's estimate:\n";
	estimate(code, pam);

	return 0;
}

double betsy(int lns) {
	return 0.05 * lns;
}

double pam(int lns) {
	return 0.03 * lns + 0.0004 * lns * lns;
}

void estimate(int lines, double (*pf)(int)) {
	using namespace std;
	cout << lines << " lines will take ";
	cout << (*pf)(lines) << " hour(s)\n";
}

6.11 总结

        函数是C++的编程模块。要使用函数,必须提供定义和原型,并调用该函数。函数定义是实现函数功能的代码,函数原型描述了函数的接口,传递给函数的值的数目和种类以及函数的返回类型。函数调用使得程序将参数传递给函数,并执行函数的代码。

        在默认情况下,C++函数按值传递参数。这意味着函数定义中的形参是新的变量,它们被初始化为函数调用所提供的值。因此,C++函数通过使用拷贝,保护了原始数据的完整性。

        C++将数组名参数视为数组第一个参数的地址。从技术上讲,这仍然是按值传递的,因为指针是原始地址的拷贝,但函数将使用指针来访问原始数据的内容。当且仅当声明函数的形参时,下面两个声明才是等价的:

        typeName arr[];

        typeName* arr;

        这两个声明都表明,arr是指向typeName的指针,但在编写函数代码时,可以像使用数组名那样使用arr来访问元素:arr[i]。即使在传递指针时,也可以将形参声明为const指针,来保护原始数据的完整性。由于传递函数地址时,并不会传输有关数组的长度信息,因此通常将数组长度作为独立参数来传递。另外,也可以传递两个指针(其中一个指向数组开头,另一个指向数组末尾的下一个元素),以指定一个范围,就像STL使用的算法一样。

        C++提供了3中表示C-风格字符串的方法:字符数组、字符串常量和字符串指针。它们的类型都是char*(char指针),因此被作为char*类型参数传递给函数。C++使用空值字符(\0)来结束字符串,因此字符串函数检测空值字符来确定字符串的结尾。

        C++还提供了string类,用于表示字符串。函数可以接受string对象作为参数以及将string对象作为返回值。string类的方法size()可用于判断其存储的字符串的长度。

        C++处理结构的方式与基本类型完全相同,这意味着可以按值传递结构,并将其用作函数返回类型。然而,如果结构非常大,则传递结构指针的效率将更高,同时及函数能够使用原始数据。这些考虑因素也适用于类对象。

        C++函数是可以递归的,也就是说,函数代码中可以包括对函数本身的调用。

        C++函数名与函数地址的作用相同。通过将函数指针作为参数,可以传递要调用的函数的名称。

  • 20
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值