一、函数的基本知识
在创建自己的C++函数,必须完成如下三个要求:提供函数定义、提供函数原型、调用函数。
1.定义函数
将函数分成两类:没有返回值的函数和有返回值的函数。
没有返回值的函数被称为void函数,通用格式如下:
void functionName(parameterList)//parameterList指定了传递给函数的参数类型和数量
{
statement(s)
return ; //optional
}
将cheers!打印指定次数(n)的函数如下:
void cheers(int n)
{
for (int i = 0; i < n; i++)
std::cout << "cheers!";
std::cout << std::endl;
}
有返回值得函数将生成一个值,并将它返回给调用函数,其通用格式:
typeName functionName(parameterList)
{
statement
return value;//value is type cast to type typeName
}
int bigger(int a ,int b)
{
if (a > b)
return a; // if a > b, function terminates here
else
return b; // otherwise, function terminates here
}
2.函数原型和函数调用
a.原型描述了函数到编译器的接口,也就是说,他将函数的返回值的类型以及参数的类型和数量告诉编译器。
b.函数原型是一条语句,因此必须以分号结束。
c.原型的功能确保:编译器正确处理函数的返回值;编译器检查使用的参数数目是否正确;编译器检查使用的参数类型是否正确,如果不正确则尽可能转换为正确的类型。
#include <iostream>
void cheers(int); // prototype: no return value
double cube(double x); // prototype: returns a double
int main()
{
using namespace std;
cheers(5); // function call
cout << "Give me a number: ";
double side;
cin >> side;
double volume = cube(side); // function call
cout << "A " << side <<"-foot cube has a volume of ";
cout << volume << " cubic feet.\n";
cheers(cube(2)); // prototype protection at work
// cin.get();
// cin.get();
return 0;
}
void cheers(int n)
{
using namespace std;
for (int i = 0; i < n; i++)
cout << "Cheers! ";
cout << endl;
}
double cube(double x)
{
return x * x * x;
}
都是从main的函数开始执行。main函数中cheers(5)调用void cheers(int n)函数,打印出5个Cheers!再输入一个数side,调用cube三次方的函数。
二、函数参数和按值传递
C++通常按值传递参数,这意味着将数值传递给函数,而后者将其赋给一个新的变量。如
double volume = cube(side);
side是一个变量,其值为5,cube()的函数头如下:
double cube (double x)
被调用的时候,该函数将创建一个新的名为x的double变量,并将其初始化为5,这样,cube()执行的操作将不会影响main()中的数据,因为cube()使用的是side的副本,而不是原来的数据。
1.多个参数
函数可以又多个参数。在定义和调用函数时,只需使用逗号将这些参数隔开即可。
void n_chars(char c,int n)
该函数头指出,函数n_char接受一个char 和int 参数,传递给函数的值被赋给参数c和n;如果函数的两个参数类型一致,也需要分别指出每个参数的类型。原型中的变量名不必与定义中的变量名相同,可以省略。
void n_chars(char ,char)
#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') // q to quit
{
cout << "Enter an integer: ";
cin >> times;
n_chars(ch, times); // function with two arguments
cout << "\nEnter another character or press the"
" q-key to quit: ";
cin >> ch;
}
cout << "The value of times is " << times << ".\n";
cout << "Bye\n";
// cin.get();
// cin.get();
return 0;
}
void n_chars(char c, int n) // displays c n times
{
while (n-- > 0) // continue until n reaches 0
cout << c;
}
while (n-- > 0) // continue until n reaches 0
cout << c;
在函数中修改参数的值不会影响调用程序中的数据。程序通过将n的变量递减来计数,其中n是参数列表的形参,main()中的times变量的值被赋给该变量。然年,while循环将n减到0,修改n的值对times没有影响。即使在函数main()中使用名称n而不是times,在函数n_chars()中修改n的值时,也不会影响函数main()中的值。
2.另外一个接受两个参数的函数
#include <iostream>
long double probability(unsigned numbers, unsigned picks);
int main()
{
using namespace std;
double total, choices;
cout << "Enter the total number of choices on the game card and\n"
"the number of picks allowed:\n";
while ((cin >> total >> choices) && choices <= total)
{
cout << "You have one chance in ";
cout << probability(total, choices); // compute the odds
cout << " of winning.\n";
cout << "Next two numbers (q to quit): ";
}
cout << "bye\n";
// cin.get();
// cin.get();
return 0;
}
long double probability(unsigned numbers, unsigned picks)
{
long double result = 1.0; // here come some local variables
long double n;
unsigned p;
for (n = numbers, p = picks; p > 0; n--, p--)
result = result * n / p ;
return result;
}
这是一个从n个数里面选出p个,中奖的概率。可以在函数中使用两种局部变量,首先形参(number 和picks),这是在左括号前面的函数头 中声明的,其次是其他的局部变量(result、n和p)他们是在将函数定义括起的括号内声明的。形参和其他局部变量的主要区别的hi,形参从调用函数那获得自己的值,而其他变量的从函数中获得自己的值。
三、函数和数组
#include <iostream>
const int ArSize = 8;
int sum_arr(int arr[], int n); // prototype
int main()
{
using namespace std;
int cookies[ArSize] = {1,2,4,8,16,32,64,128};
// some systems require preceding int with static to
// enable array initialization
int sum = sum_arr(cookies, ArSize);
cout << "Total cookies eaten: " << sum << "\n";
// cin.get();
return 0;
}
// return the sum of an integer array
int sum_arr(int arr[], int n)
{
int total = 0;
for (int i = 0; i < n; i++)
total = total + arr[i];
return total;
}
int sum_arr(int arr[],int n) 方括号指出arr是一个数组,而方括号为空则表明,可以将任何长度的数组传递给该函数。但是实际情况,arr并不是一个数组,而是一个指针。但是可以将arr看作一个数组。
1.函数如何使用指针来处理数组
C++将数组名解释为其第一个元素的地址:
cookies==&cookies[0] //array name is address of first element
首先,数组声明使用数组名来标记存储位置;其次,对数组名使用sizeof将得到整个数组的长度;第三,将地址运算符&用于数组名时,将返回整个数组的地址。
函数调用:int sum = sum_arr(cookies ,ArSize)
因为cookies是数组名,根据C++规则,cookies是其第一个元素的地址,因此函数传递的是地址,由于数组的元素类型是int,因此cookies的类型必须是int指针,即int*。正确的函数头
int sum_arr(int * arr, int n) //arr=array name ,n=size
在C++中,当且仅当用于函数头或者函数原型中,int *arr与int arr[]的含义才是相同的。它们都意味着arr是一个int的指针,然而数组表示法提醒,arr不仅指向int还指向int数组的第一个int。
2.将数组作为参数,只是将数组的地址,类型以及数目传递给函数,并不能传递数组的内容。同时将数组地址作为参数可以节省复制整个数组所需要的时间和内存。
#include <iostream>
const int ArSize = 8;
int sum_arr(int arr[], int n);
// use std:: instead of using directive
int main()
{
int cookies[ArSize] = {1,2,4,8,16,32,64,128};
// some systems require preceding int with static to
// enable array initialization
std::cout << cookies << " = array address, ";
// some systems require a type cast: unsigned (cookies)
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); // a lie
std::cout << "First three eaters ate " << sum << " cookies.\n";
sum = sum_arr(cookies + 4, 4); // another lie
std::cout << "Last four eaters ate " << sum << " cookies.\n";
// std::cin.get();
return 0;
}
// return the sum of an integer array
int sum_arr(int arr[], int n)
{
int total = 0;
std::cout << arr << " = arr, ";
// some systems require a type cast: unsigned (arr)
std::cout << sizeof arr << " = sizeof arr\n";
for (int i = 0; i < n; i++)
total = total + arr[i];
return total;
}
cookies和arr指向同一个地址,但sizeof cookies的值为32,而sizeof arr为4,这是由于sizeof cookies是整个数组的长度,而sizeof arr只是指针变量的长度。
sum = sum_arr (cookies,3)告诉cookies有3个元素,可以让他计算前3个元素的总和。
sum = sum_arr (cookies+4,4);由于cookies是第一个元素的地址,因此cookies+4是第五个元素的地址,所以是计算低5、6、7、8个元素的总和。
3.使用数组区间的函数
对于处理C++的函数,必须将数组中的数据种类、数组的起始位置和数组中元素的数量提交给他。
#include <iostream>
const int ArSize = 8;
int sum_arr(const int * begin, const int * end);
int main()
{
using namespace std;
int cookies[ArSize] = {1,2,4,8,16,32,64,128};
nt sum = sum_arr(cookies, cookies + ArSize);
cout << "Total cookies eaten: " << sum << endl;
sum = sum_arr(cookies, cookies + 3); // first 3 elements
cout << "First three eaters ate " << sum << " cookies.\n";
sum = sum_arr(cookies + 4, cookies + 8); // last 4 elements
cout << "Last four eaters ate " << sum << " cookies.\n";
// cin.get();
return 0;
}
// return the sum of an integer array
int sum_arr(const int * begin, const int * end)
{
const int * pt;
int total = 0;
for (pt = begin; pt != end; pt++)
total = total + *pt;
return total;
}
4.指针和const
可以用两种不同的方式将const关键字用于指针。一是让指针指向一个常量对象,这样可以防止使用该指针来修改所指向的值;二是将指针本身声明为常量,这样可以防止改变指针指向的位置。
声明一个指向一个常量的指针pt:
int age = 39;
const int * pt =&age;
pt指向一个const int(这里为39),因此不能使用pt开修改这个值,也就是说,*pt的值为const,不能被修改。这里是将常规的地址赋给指向const的指针。
pt的声明并不意味着它指向的值实际上就是一个常量,而只是意味着对pt而言这个值是常量。pt指向age,而age不是const。可以直接通过age的变量来修改age的值,而不能通过pt指针来修改。
此外,将const变量的地址赋给指向const的指针是可行的,将const的地址赋给常规指针是不可行的。
const float g_earth =9.80;
const float * pe =&g_earth;
既不能使用g_earth来修改值9.80,也不能使用pe的值来修改。
尽可能使用const,将指针参数声为指向常量数据的指针理由:1.这样可以避免由于无意间修改数据而导致的编程错误;2.使用const使得函数能够处理const和非const实参,否则只能接受非const数据。此外,应尽量将指针形参声明为指向const的指针。
四、函数和二维数组
为编写将二维数组作为参数的函数,数组名被视为其地址,因此,相应的形参是一个指针,就像一维数组一样。
int sum(int ar2[][4],int size)
ar2是指针而不是数组,指针类型指出,它指向由4个int组成的数组。
五、函数和C-风格字符串
1.将C-风格字符串作为参数的函数
假设要将字符串作为参数传递给函数,则表示字符串的方式有三种:
char数组;
用引号括起的字符串常量;
被设置为字符串的地址的char指针。
将字符串作为参数来传递,但实际传递的是字符串的第一个字符的地址,这意味着字符串函数原型应该将其表示字符串的形参声明为char*类型。
2.返回C-风格字符串的函数
函数无法返回一个字符串,但是可以返回字符串的地址。
#include <iostream>
unsigned int c_in_str(const char * str, char ch);
int main()
{
using namespace std;
char mmm[15] = "minimum"; // string in an array
// some systems require preceding char with static to
// enable array initialization
char *wail = "ululate"; // wail points to string
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;
// cin.get();
return 0;
}
/ this function counts the number of ch characters
// in the string str
unsigned int c_in_str(const char * str, char ch)
{
unsigned int count = 0;
while (*str) // quit when *str is '\0'
{
if (*str == ch)
count++;
str++; // move pointer to next char
}
return count;
}
定义buildstr()的函数,该函数返回一个指针。该函数接受两个参数:一个字符和一个数字。
六、函数和结构
1、传递和返回结构
#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}; // 5 hrs, 45 min
travel_time day2 = {4, 55}; // 4 hrs, 55 min
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));
// cin.get();
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";
}
travel_time就像一个标准的类型名,可被用来声明变量、函数的返回类型和函数的参数类型。2.另一个处理结构的函数示例
距离和角度构成了极坐标
struct polar
{
double distance;
double angle;
}
distance=sqrt(xx+yy)
angle=atan2(y,x)
3.传递结构的地址
假设要传递结构的地址而不是整个结构以节省时间和空间,则需要重新编写函数,使用指向结构的指针。
七、函数和string对象
与数组相比,string对象与结构的更相似,可以一个结构赋值给一个结构,也可以一个对象赋值给一个对象。如果需要多个字符串,可以声明一个string对象数组,而不是二维char数组。
#include <iostream>
#include <string>
using namespace std;
const int SIZE = 5;
void display(const string sa[], int n);
int main()
{
string list[SIZE]; // an array holding 5 string object
cout << "Enter your " << SIZE << " favorite astronomical sights:\n";
for (int i = 0; i < SIZE; i++)
{
cout << i + 1 << ": ";
getline(cin,list[i]);
}
cout << "Your list:\n";
display(list, SIZE);
// cin.get();
return 0;
}
void display(const string sa[], int n)
{
for (int i = 0; i < n; i++)
cout << i + 1 << ": " << sa[i] << endl;
}
八、函数与array对象
在C++中,类对象是基于结构的,因此结构编程方面的有些考虑因素也适用于类。
#include <iostream>
#include <array>
#include <string>
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, 4> expenses;
fill(&expenses);
show(expenses);
// std::cin.get();
// std::cin.get();
return 0;
}
void fill(std::array<double, Seasons> * pa)
{
for (int i = 0; i < Seasons; i++)
{
std::cout << "Enter " << Snames[i] << " expenses: ";
std::cin >> (*pa)[i];
}
}
void show(std::array<double, Seasons> da)
{
double total = 0.0;
std::cout << "\nEXPENSES\n";
for (int i = 0; i < Seasons; i++)
{
std::cout << Snames[i] << ": $" << da[i] << '\n';
total += da[i];
}
std::cout << "Total: $" << total << '\n';
}
九、递归
C++有趣的功能,可以自己调用自己,称之为递归。
函数代码中可以包括对函数本身的调用。
#include <iostream>
void countdown(int n);
int main()
{
countdown(4); // call the recursive function
// std::cin.get();
return 0;
}
void countdown(int n)
{
using namespace std;
cout << "Counting down ... " << n << endl;
if (n > 0)
countdown(n-1); // function calls itself
cout << n << ": Kaboom!\n";
}
十、函数指针
函数的地址是存储其机器语言代码的内存的开始地址。
1.获取函数的地址,只要使用函数名(后面不跟参数)即可。也就是说,如果think()是一个函数,则think就是该函数的地址。另外,要将函数作为参数进行传递,必须传递函数名。
2.声明函数的指针,声明指向某种数据类型的指针,必须指定指针的指向类型。
原型:double pam(int);
正确的指针类型声明:double (*pf)(int)
pf就是这类函数的指针。
3.使用指针来调用函数
使用(*pf)只需将他看作函数名即可。
#include <iostream>
double betsy(int);
double pam(int);
// second argument is pointer to a type double function that
// takes a type int argument
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);
// cin.get();
// cin.get();
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";
}
两次调用estimate()函数,一次传递betsy()函数的地址,另一次则传递pam()函数的地址。
函数名与函数地址的作用相同,通过将函数指针作为参数,可以传递要调用的函数名称。