C++入门——Day6_C++函数编程模块

一、复习函数的基本知识

库函数

自定义函数

先写段程序,复习下自定义函数

#include <iostream>

void simple(void);
using namespace std;

int main(void)
{

    cout << "main() will call the somple() function " << endl;
    simple();

    return 0; 
}
void simple(void)
{
    cout << "I'm simple but a function." << endl;
}

自定义函数的三个部分:

函数定义,函数声明,函数调用,缺一不可

1:定义函数

函数分为有返回值和没有返回值的

没有返回值就是void函数,通用格式如下:

void functionName (parameterList)

{

        statement(s)

        return;   //可写可不写,没返回值一般不写

}

其中的paramenterList是指传递给函数的参数类型和数量,例如:
void cheers (int n)

{

         for(int i = 0; i <n ; i++)

                cout << "Cheers!";

        cout << endl;

}

这里的int意思就是,在调用cheers函数的时候,应该给它一个Int值作为参数传递给它

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

typeName functionName(paramterList)

{

         statements;

        return value;

}

对于有返回值的函数,必须使用返回语句,以便把值返回给调用函数。

值本身可以是常量、变量、表达式,只是其结果的类型必须为typeName类型或者可以被转换为typeName(比如说我们返回类型是double,但是返回一个int表达式,int会被强转为double类型)

C++对于返回值的类有限制:不能是数组,但是可以是其他任何类型,比如结构对象等(但是可以把数组作为结构或对象组成部分来返回)

函数在执行返回语句后结束。如果函数包含多条返回语句,则函数在执行遇到第一条返回语句后结束。

int bigger (int a , int b)

{

        if(a>b)

                return a;

        else

                return b;

}

2:函数原型和函数调用

看如下程序,体会自定义有返回值和没有返回值的函数

#include <iostream>

using namespace std;
void cheers(int n);//声明
double cube(double x);

int main(void)
{
    cheers(5);//调用
    
    cout << "Give me a number: " << endl;
    double side;
    cin >> side;
    
    double volume = cube(side);
    cout << side << " side cube = " << volume << endl;

    cheers(cube(2));  //自定义函数调用自定义函数,并进行double转int的操作

    return 0; 
}
void cheers(int n)      //无返回值
{
    for (int i = 0; i < n; i++)
    {
        cout << "Cheers!" << endl;
    }
}
double cube(double x)     //有返回值
{
    x = x * x * x;
    return x;
}

结果:

Cheers!
Cheers!
Cheers!
Cheers!
Cheers!
Give me a number:
3
3 side cube = 27
Cheers!
Cheers!
Cheers!
Cheers!
Cheers!
Cheers!
Cheers!
Cheers!

*

1)自定义无返回值cheers,自定义有返回值cube

2)把输入作为cube的参数,返回double类型

3)自定义函数cheers引用自定义函数cube

1)为什么需要原型?
就是头文件下面为什么要再声明一下

函数原型描述了函数到编译器的结构,也就是它将函数的返回值类型及参数类型数量告诉编译器

cube()函数完成计算后,把返回值放到了指定位置——可能是寄存器,可能是内存,然后调用函数(main())从这个为之取得返回值

C++编译器并不会在文件中进一步查找,这样效率太低了,所以最好能提供原型

2)原型的语法

函数原型并不需要提供变量名,有类型列表有足够了。比如对于cheer()原型,我们只提供了参数类型:

void  cheers(int);

比方说我们去饭店吃饭,只需要打电话告诉饭店,我们有几个人,我们吃什么菜,我们有几个人,而不需要把每个人的名字告诉饭店一个道理

所以说比方说下面的原型:

double cube(double x)写成double cube (double) 一样是可以编译的!

3)原型的功能

要确保以下几点:

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

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

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

但是如果是较大的数据类型转换为较小类型时,有些编译器会发出警告,指出数据可能丢失

比如把2.33E27转为int类型,这种值就不会被正确转换

二、函数参数和按值传递

C++通常按值传递参数,这意味着将数值参数传递给函数,而后者将其赋给新的变量

比如:double volume = cube  (side);

→   side是一个变量,前面定义为5

cube的函数头如下:double cube (double x)

→   函数被调用时,该函数创建一个新的名为x的double变量,把它初始化为5

→    这样cube()执行的操作将不会影响main()中的数据。

→    cube使用的是side的副本,而不是原来的数据

用于接收传递值得变量被称为形参,比如这里double 的x

传递给函数的值被称为实参,比如原来的side

1:多个参数

1)函数内定义两个变量,哪怕是相同类型,也要写全

void fifi (float a, float b)

2)与一个参数一样,原型中的变量名不必与定义中的变量名相同,甚至可以省略,但是最好写上,至少知道人家是干嘛用的

见如下程序:演示修改形参并不会影响调用函数中的数据

#include <iostream>

using namespace std;

void n_chars(char c, int n);

int main(void)
{
    char ch;
    int times;

    cout << "Please enter a character: ";
    cin >> ch; //cin.get()也行

    while(ch != 'q')
    {
        cout << "Enter a integer: ";
        cin >> times;

        n_chars(ch,times);
        cout << endl;
        cout << "Enter another character or press q_key to quie: " << endl;
        cin >> ch;
    }
    return 0; 
}

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

结果:

Please enter a character:

1
Enter a integer: 10
1111111111
Enter another character or press q_key to quie:
q

 

*

1)声明了ch和times分别作为n_chars的两个参数,然后通过函数返回内容

2:另外一个接收两个参数的函数

题目如下:

美国许多州都采用某种纸牌游戏来发行彩票,让参与者从卡片中选择一定数目的选项。

例如:从51个数字中选取6个,随后管理者随机抽取6个数。如果参与者选择的数字与这6个完全相同,赢得几百万美金。函数计算中奖概率

首先,必须从51个数中选取6个数,而中奖的概率为1/R,R的计算公式为:

R=\frac{51\times 50\times 49\times 48\times 47\times 46}{6\times 5\times 4\times 3\times 2\times 1}

选择6个数时,分母为6!,分子是连续6个整数的乘积,从51开始连续递减,可以采用for循环计算

long double result = 1.0;

for(n = numbers , p = picks ; p > 0 ; n-- , p--)(numbers是一共从多少个数中选择)

        result = result * n / p;(pick是选取数的个数)

程序:

#include <iostream>

using namespace std;
long double probability(unsigned int numbers,unsigned int picks);

int main(void)
{
    unsigned int total,choices;
    cout << "Enter the total number of choices on the game card and the number of picks allowed: " << endl;
    while((cin >> total >> choices) && choices <= total)
    {
        cout << "You have one chance in " << probability(total,choices) << " of winning";
        cout << "Please enter next two number(q to quit)";
    }
    cout << "Bye";
    
    return 0; 
}
long double probability(unsigned int numbers,unsigned int picks)
{
    double n,p;
    long double result = 1.0;

    for(n = numbers, p = picks; p > 0; n--,p--)
        result = result * (n / p);

    return result;
}

结果(输出是R,概率是1/R):

Enter the total number of choices on the game card and the number of picks allowed:
49
6
You have one chance in 1.39838e+07 of winningPlease enter next two number(q to quit)100
10
You have one chance in 1.73103e+13 of winningPlease enter next two number(q to quit)

*

1)这个程序就演示了可以在函数中使用两种局部变量。

首先是形参(numbers和picks);其次是其他局部变量(result、n、p)

形参和局部变量的主要区别是,形参从调用函数那里获得自己的值,而其他变量是从函数中获得自己的值

2)注意可以这样写:

while((cin >> total >> choices) && choices <= total),代表如果输入成功

三、函数和数组

如何把函数和数组结合起来?

假设使用一个数组来记录家庭野餐中每人吃了多少甜品,现在要统计总数,来使用函数实现

我们返回值部分不能为数组,但是参数部分可以是

比如以下声明:

int   sum_arr (  int arr[] , int n )

这里的arr看起来是一个数组,其实它是一个指针,但是函数编写时,可以把它看成是数组

下面程序将演示如同使用数组名那样使用指针

#include <iostream>

using namespace std;

const int ArSize = 8;

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

int main(void)
{
    int cookies[ArSize] = {1,2,4,8,16,32,64,128};//数组定义好了

    int sum = sum_arr(cookies,ArSize); //函数返回总数
    cout << "Total cookies eaten: " << sum << endl;
    
    return 0; 
}
int sum_arr(int arr[],int n)//它用了两个参数,一个是数组,一个是数组数量
{
    int total = 0;
    for(int i = 0; i < n; i++)//很明显,我们用数组的数量和求和
    {
        total += arr[i];
    }
    return total;
}

结果:Total cookies eaten: 255

1:函数如何使用指针处理数组

大多数情况下,C++和C一样,也将数组名视为指针,它将数组名解释为第一个元素的地址:

cookies  ==  & cookies [0]

但是也有一些例外,比如

1)数组声明使用数组名来标记存储位置

2)对数组名使用sizeof将得到整个数组的长度(字节为单位)

3)&用于数组名时,返回整个数组的地址

再来看这个函数调用:int sum = sun_arr(cookies , ArSize)

cookies是数组名,而C++规定它是第一个元素的地址,因此函数传递的是地址。所以cookies类型必须是int指针,即int *。所以正确的函数头应该是:
in sun_arr ( int * arr , int n)

在函数头或函数原型中,int * arr和int arr[]含义相同,它们都意味着arr是一个int指针

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

&arr [i]  ==  ar +  i

将指针(数组名+1)意味着加上了一个与指针指向的类型的长度相等的值,就是指针偏移

2:将数组作为参数意味着什么?

sum_arr把cookies的第一个元素的地址和数组中的元素传递给了sum_arr函数。

sum_arr函数将cookies的地址赋给指针变量arr,将ArSize赋给int变量n

函数在传递数组的时候,使用的是原来的数组

看下面的函数,这回我们使用指针来演示上面的程序

#include <iostream>

using namespace std;

const int ArSize = 8;

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

int main(void)
{
    int cookies[ArSize] = {1,2,4,8,16,32,64,128};//数组定义好了
    cout << "array adress: " << &cookies << endl;
    cout << "size of cookies: " << sizeof(cookies) << endl; //32

    int sum = sum_arr(cookies,ArSize); //函数返回总数
    cout << "Total cookies eaten: " << sum << endl;

    sum = sum_arr(cookies,3);
    cout << "3 cookies eaten: " << sum << endl;
    
    return 0; 
}
int sum_arr(int arr[],int n)//它用了两个参数,一个是数组,一个是数组数量
{
    int total = 0;
    cout << "arr adress: " << arr << endl;
    cout << "size of arr: " << sizeof arr << endl;//这里返回的不是数组,而是指针,8个字节,int *占用8字节

    for(int i = 0; i < n; i++)//很明显,我们用数组的数量和求和
    {
        total += arr[i];
    }
    return total;
}

结果:

array adress: 0x3cebbff7d0
size of cookies: 32
arr adress: 0x3cebbff7d0
size of arr: 8
Total cookies eaten: 255
arr adress: 0x3cebbff7d0
size of arr: 8
3 cookies eaten: 7

*

1)

cout << "size of cookies: " << sizeof(cookies) << endl; 这里打印的是数组的大小

2)cout << "arr adress: " << arr << endl;

我们sum_array的地址和cookies地址完全一样,所以我们知道sum_array其实就是使用了cookies的地址来进行运算

3)cout << "size of arr: " << sizeof arr << endl;//这里返回的不是数组,而是指针,8个字节,int *占用8字节

3:更多数组函数示例

先看一个案例:考虑对房地产数组执行的操作。两个基本操作是,将值读入到数组中和显示数组的内容。

另外添加另一个操作:重新评估每种房地产的值。(所有房地产都以相同的比率增加或减少)

#include <iostream>

using namespace std;
const int Max = 5;

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

int main(void)
{
    
    double properties[Max];//5个double类型数组
   
    int size = fill_array(properties,Max);//添加数组的方法
    
    show_array(properties,size);

    if(size > 0)
    {
        cout << "Enter revaluation factor: ";
        double factor;

        while(!(cin >> factor))
        {
            cin.clear();
            while ((cin.get() != '\n'))
            {
                continue;
            }
            cout << "Bad input : input process terminated." << endl;      
        }
        revalue(factor,properties,size);

        show_array(properties,size);
    }
    cout << "Done!\n";
    cin.get();
    cin.get();
    return 0; 
}
int fill_array(double arr[],int limit)//
{
    double temp;
    int i;
    for (i = 0; i < Max; i++)
    {
        cout << "Enter value #" << i + 1 << ":";
        cin >> temp;
        if(!cin)
        {
            cin.clear();
            while (cin.get() != '\n')
            {
                continue;
            }
            cout << "Bad input : input process terminated." << endl;
            break;
        }
        else if(temp < 0)
            break;
        else
            arr[i] = temp;
    }
    return i;
}
void show_array(const double arr[],int n)//加上const以后,就不能通过指针来修改对象了
{
    for (int i = 0; i < n; i++)
    {
        cout << "Property #" << i+1 << " : $";
        cout << arr[i] << endl;
    } 
}
void revalue(double r,double arr[], int n)//r是比例系数
{
    for(int i = 0; i < n ; i++)
    {
        arr[i] *= r;
    }
}

结果:

输入:
Enter value #1:1000
Enter value #2:2000
Enter value #3:3000
Enter value #4:4000
Enter value #5:5000

显示:

Property #1 : $1000
Property #2 : $2000
Property #3 : $3000
Property #4 : $4000
Property #5 : $5000

修改因子:
Enter revaluation factor: 2
Property #1 : $2000
Property #2 : $4000
Property #3 : $6000
Property #4 : $8000
Property #5 : $10000
Done!

*程序说明:

1)填充数组

该函数指定两个参数,一个是数组名,另一个是指定读取的最大元素数,该函数返回实际读取的元素数,还要注意判断它输入是否正确和输入是否大于0这两个条件,然后使用for循环,创建临时值,挨个放进去

2)显示数组

这个就是循环+显示数组元素

3)修改数组

它需要给函数传递三个参数,分别是因子,数组和元素数目,没有返回值

4:使用数组区间和函数

除了上面例子中提供数组中的数据类型、数组的起始位置和数组中元素数量,还有另一种方法:

即指定元素区间(range),这可以通过传递两个指针来完成:一个指针标识数组开头,另一个指针标识数组的尾部,看下例(还是饼干):

#include <iostream>

using namespace std;

const int ArSize = 8;

int sum_arr(const int * begin, const int * end);

int main(void)
{
    int cookies[ArSize] = {1,2,4,8,16,32,64,128};

    int sum = sum_arr(cookies, cookies + ArSize); 
    cout << "Total cookies eaten: " << sum << endl;
    
    return 0; 
}
int sum_arr(const int * begin, const int * end)
{
    int total = 0;
    const int * pt;

    for(pt = begin; pt != end; pt++)
        total += *pt; 
    return total;
}

结果:Total cookies eaten: 255

*

1)定义了指针的开头和指针的结尾,求和就用指针内容

2)调用函数的开头结尾位置采用cookies和cookies + ArSize

5:指针和const

三个const的用法

①const int *pt

②int *const pt;

③const int *const pt;

①const int *pt等效于  int const *pt

使用const,就不能通过指针修改数组了,比如如下程序:

int main(void)
{
    int n =10;
    int *pt = &n;

    cout << "1)n= " << n << endl;
    
    *pt = 20;//通过指针修改了n的对象
    cout << "2)n= " << n << endl;
    
    return 0; 
}

1)n= 10
2)n= 20

但是如果加上const,就不能通过指针来修改了

int main(void)
{
    int n =10;
    const int *pt = &n;

    cout << "1)n= " << n << endl;
    
    *pt = 20;//通过指针修改了n的对象
    cout << "2)n= " << n << endl;
    
    return 0; 
}

结果如下:

 报错说*pt是只读,所以不能通过const修改后的指针来修改原值

但是指针还是可以指向其他地方的,如下:

#include <iostream>

using namespace std;


int main(void)
{
    int n =10;
    int m = 100;
    const int *pt = &n;

    cout << "1)n= " << n << endl;
    
    //*pt = 20;//通过指针修改了n的对象
    pt = &m;
    cout << "*pt = " << *pt << endl;
    cout << "m = " << m << endl;
    
    return 0; 
}

还是可以指向其他位置,比如m

1)n= 10
*pt = 100
m = 100

②int * const pt;   

#include <iostream>

using namespace std;

int main(void)
{
    int n =10;
    int *const pt = &n;

    cout << "1)n= " << n << endl;
    *pt = 20;
    cout << "2)n = " << n << endl;
    
    return 0; 
}

这时候把*放到了const的前面,就可以通过pt指针修改原n值

结果:
1)n= 10
2)n = 20

再把pt指针指向m

#include <iostream>

using namespace std;

int main(void)
{
    int n =10;
    int *const pt = &n;

    cout << "1)n= " << n << endl;
    *pt = 20;
    cout << "2)n = " << n << endl;

    pt = &m;
    
    return 0; 
}

这时候就报错了,说明*const pt可以修改指向数值的值,但是只能指向一个值,不能指向任意变量

③const int *const pt;

这就说明了,你既不能通过指针修改内容,也不能指向其他位置

尽可能使用const

将指针参数声明为指向常量数据的指针有两条理由:

·这样可以避免由于无意间修改数据而导致的编程错误

·使用const使得函数能够处理const和非const实参,否则将只能接收非const数据

如果条件允许,则应将指针形参声明为指向const的指针

如果函数形参是const,那么不管函数是const或者非const,都能进行调用

如果函数形参是非const,那么如果函数是const,就不能进行调用

所以在使用的时候,最好给形参设置成const指针!

四、函数和二维数组

1:

跟一维数组类似,假设

int date[3] [4] = { {1,2,3,4},{5,6,7,8},{2,4,6,8}};

int total =sum(data , 3),sum函数就是数组名+数组中的元素

data是一个数组名,有三个元素,第一个元素本身是数组,有4个int组成,因此data类型是指向由4个int组成的数组的指针,正确的原型如下:

int sum(int(*ar2)[4], int size))(指针的数组)

这声明了将4个指向int的指针组成的数组

而不是由一个指向由4个int组成的数组的指针

如:int ar2[4],(这是数组的指针)

二维数组函数写成下面这样子,可读性更强;

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

2:二维数组遍历

因此二维数组的遍历就需要循环嵌套,外层访问行,内层访问列

int sum(int are[][4],int size)
{
    int total = ;
    for(int r = 0; r < size; r++)
    {
        for(int c = 0;c <4; c++)
        {
            total += ar2[r][c];
        }
    }
    return total;
}

3:提取元素

可以用数组表示法的原因是:ar2指向数组的第一个元素,因此ar+r指向编号为r的元素,又因为元素本身又是数组,随意ar2[r]是由4个int组成的数组的名称,所以ar2[r][c]是4个int组成的数组中的一个元素(最简单的方法),必须对指针执行两次才能解除引用,如下:
 

ar2[r][c]  ==  *(*(ar2+r)+c)

先对行指针+r,利用*取出行元素,就是数组,然后对数组名+c,就是针对这个数组偏移,然后再*取出元素



五、函数和C风格字符串

1:表示字符串的三种方法

·char数组     char ghost[15] = "galloping";

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

·被设置为字符串的地址的char指针       char * str = "galumping"

看如下例子:使用一个函数来计算特定的字符再字符串中出现的次数

#include <iostream>

using namespace std;

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

int main(void)
{
    char mmm[15] = "minimum";//第一种字符数组的方式
    const char * wail = "ululate"; //第二种字符指针方式 //char * wail = (char *"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;
}

3 m characters in minimum
2 u characters in ululate

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

函数无法返回字符串,但可以返回字符串的地址,这样效率更高

下面定义一个buildstr()函数,返回一个指针,接收两个参数,一个字符一个数字。函数使用new创建一个长度和数字参数相等的字符串,然后将每个元素都初始化为该数组

#include <iostream>

using namespace std;
char * buildstr(char c, int n);

int main(void)
{
    char ch;
    cout << "Enter a character: ";
    cin >> ch;

    int times;
    cout << "Enter a integer: ";
    cin >> times;

    char *ps = buildstr(ch,times);
    cout << ps << endl;
    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];//new创建数组
    pstr[n] = '\0';//最后一个留给空字符
    for(int i = 0 ; i < n; i++)
    {
        pstr[i] = c;
    }
    return pstr;
}

结果:

Enter a character: V
Enter a integer: 20
VVVVVVVVVVVVVVVVVVVV
++++++++++++++++++++-Done-++++++++++++++++++++

*

1)要注意new字符串的时候多留一位给我们的空格

2)new完了要记得delete

六、函数和结构

结构变量的行为更接近基本的单值变量,在这种情况下,函数将使用原始结构的副本,另外,函数也可以返回结构

与数组名就是数组的第一个元素的地址不同,结构名只是结构的名称,要获得结构的地址,必须使用地址运算符&

如果结构非常大,则赋值结构将增加内存要求,所以许多C程序员进行函数传递和返回,更喜欢指针来访问结构体的内容

C++提供了第三种选择——按引用类型

1:传递和返回结构

看如下程序:

#include <iostream>

using namespace std;
const int Mins_per_hr = 60;

struct travel_time
{
    int hours;
    int mins;
};

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

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

    travel_time trip = sum(day1,day2);

    cout << "Two days :" << trip.hours << " hours , " << trip.mins << " mins." << endl;//这样写还是有些复杂,再定义一个shouwtime函数

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

    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;   //  (t1.mins + t2.mins) / Mins_per_hr会自动取整的

    return total;
}
void show_time(travel_time t)
{
    cout << t.hours << " Hours, " << t.mins << "Minutes." << endl;//没返回值
}

结果:

Two days :10 hours , 40 mins.
Three days :15 Hours, 12Minutes.

*这里所有的参数类型都是结构体

2:另一个处理结构的函数示例

定义两个结构,用于表示两种不同的描述位置的方法,然后开发一个函数,将一种格式转换为另一种格式,并显示结果

#include <iostream>
#include <cmath>

using namespace std;

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(void)
{
    rect rplace;
    polar pplace;

    cout << "Enter the x and y value: ";
    while (cin >> rplace.x >> rplace.y)
    {
        pplace = rect_to_polar(rplace);//将直角坐标系转为极坐标系,输入的一定是直角坐标系新建的
        show_polar(pplace);//要显示极坐标系,参数肯定是极坐标系
        cout << "Next two numbers(q to quit):";
    }
    return 0;
}
polar rect_to_polar(rect xypos)
{
    polar answer;
    answer.distance = sqrt(xypos.x * xypos.x + xypos.y * xypos.y);
    answer.angle = atan2(xypos.y,xypos.x);//atan2返回弧度,还需要转化为角度
    return answer;
}

void show_polar(polar dapos)
{
    const double Rad_to_deg = 57.29577951;
    cout << "Distance = " << dapos.distance << endl;
    cout << "Angel = " << dapos.angle * Rad_to_deg << "degree" << endl;
}

结果:

Enter the x and y value: 30
40
Distance = 50
Angel = 53.1301degree
Next two numbers(q to quit):-100
100
Distance = 141.421
Angel = 135degree
Next two numbers(q to quit):q

*

1)函数引用结构体类型,结构体类型应该放在最前面,不然报错

2)while (cin >> rplace.x >> rplace.y),如果输入成功

3:传递结构的地址

可以对上述的程序进行修改

#include <iostream>
#include <cmath>

using namespace std;

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(void)
{
    rect rplace;
    polar pplace;

    cout << "Enter the x and y value: ";
    while (cin >> rplace.x >> rplace.y)
    {
        //pplace = rect_to_polar(rplace);
        rect_to_polar(&rplace,&pplace);//两个值,第一个是直角坐标系的指针,第二个是极坐标系的指针
        //show_polar(pplace);

        show_polar(&pplace);//直接用地址就行了,没有返回值
        cout << "Next two numbers(q to quit):";
    }
    return 0;
}
void rect_to_polar(const rect *pxy , polar *pda)//定义两个地址,只需要把地址换了就行
{
    pda->distance = sqrt(pxy->x*pxy->x + pxy->y*pxy->y);
    pda->angle = atan2(pxy->y, pxy->x);//atan2返回弧度,还需要转化为角度
}

void show_polar(const polar *pda)
{
    const double Rad_to_deg = 57.29577951;
    cout << "Distance = " << pda->distance << endl;
    cout << "Angel = " << pda->angle * Rad_to_deg << "degree" << endl;
}

结果:

Enter the x and y value: 30
40
Distance = 50
Angel = 53.1301degree
Next two numbers(q to quit):-100
100
Distance = 141.421
Angel = 135degree
Next two numbers(q to quit):q

*

1)函数内部传递的是指针,参数是指针,没有返回值

七、函数和string对象

string对象和结构更为相似,例如,可以将一个结构赋给另一个结构,也可以将一个对象赋给另一个对象。可以将结构作为完整实体传递给函数

下面的程序声明了一个string对象数组,并将该数组传递给一个函数显示内容:

#include <iostream>

using namespace std;
const int SIZE = 5;
void display(const string sa[], int n);

int main(void)
{
    string list[SIZE];
    cout << "Enter " << SIZE << " favorite food: " << endl;
    for (int i = 0; i < SIZE; i++)
    {
        cout << i+1 << ": ";
        getline(cin,list[i]);
    }
    cout << "Your list" << endl;
    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;
    }
}

结果:

Enter 5 favorite food:
1: break
2: milk
3: icecr
4: saled
5: cake
Your list
1: break
2: milk
3: icecr
4: saled
5: cake

八、函数与array对象

假设 要使用一个array对象存储一年四个季度的开支,看如下程序:

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

using namespace std;
const int Seasons = 4;
const array<string,Seasons> Snames = {"Spring","Summer","Fall","Winter"};

void fill(array<double,Seasons> *pa);
void show(array<double,Seasons> da);

int main(void)
{
    array<double,Seasons> expenses;

    fill(&expenses);//把指针填充到array里面
    show(expenses);

    return 0;
}
void fill(array<double,Seasons> *pa)
{
    for (int i = 0; i < Seasons; i++)
    {
        cout << "Enter " << Snames[i] << " expensens: ";
        cin >> (*pa)[i];//pa是array对象的指针,需要转化成值再进行操作
    }
}
void show(array<double,Seasons> da)
{
    double total = 0.0;
    cout << "EXPENSENS: " << endl;
    for (int i = 0; i < Seasons; i++)
    {
        cout << Snames[i] << ": $" << da[i] << endl;
        total += da[i];
    }
    cout << "Total expenses: " << total << endl;
}

结果:

Enter Spring expensens: 212
Enter Summer expensens: 256
Enter Fall expensens: 208
Enter Winter expensens: 244
EXPENSENS:
Spring: $212
Summer: $256
Fall: $208
Winter: $244
Total expenses: 920
 

*cin >> (*pa)[i];//pa是array对象的指针,需要转化成值再进行操作

九、递归

C++函数又一个特点——可以自己调用自己(但是C++不允许main()调用自己),这种功能被称为递归。

1:包含一个递归调用的递归

如果递归函数调用自己,则被调用的函数也将调用自己,这将无限循环下去,除非代码又包含终止的内容,一般放在if语句中,如下:void类型的递归函数recurs()代码如下:

void recurs(argumentlist)
{

       statements1
       if(test)
            resurs(arguments)
       statements2

}

看如下的例子:

#include <iostream>

using namespace std;
void countdown(int n);

int main(void)
{
    countdown(4);
    return 0;
}

void countdown(int n)
{
    cout << "counting down.... " << n << endl;
    if(n>0)//更新条件,递归退出条件
    {
        countdown(n-1);
    }
    cout << n << ": Kandom" << endl;
}

结果:

counting down.... 4
counting down.... 3
counting down.... 2
counting down.... 1
counting down.... 0
0: Kandom
1: Kandom
2: Kandom
3: Kandom
4: Kandom

*

1)当n递减到0的时候才开始执行下面的代码:cout << n << ": Kandom" << endl;

2)第二个部分将于与函数调用相反的顺序执行5次!

3)每次调用递归都会创建自己的一套变量,因此当程序到第五次调用时,将有5个独立的n变量,其中每个变量的值都不同,对程序如下修改!

#include <iostream>

using namespace std;
void countdown(int n);

int main(void)
{
    countdown(4);
    return 0;
}

void countdown(int n)
{
    cout << "counting down.... " << n << "(n at adress: " << &n << ")" << endl;
    if(n>0)//更新条件,递归退出条件
    {
        countdown(n-1);
    }
    cout << n << ": Kandom" << "(n at adress: " << &n << ")" << endl;
}

结果如下:

counting down.... 4(n at adress: 0x1d3adffc10)
counting down.... 3(n at adress: 0x1d3adffbe0)
counting down.... 2(n at adress: 0x1d3adffbb0)
counting down.... 1(n at adress: 0x1d3adffb80)
counting down.... 0(n at adress: 0x1d3adffb50)
0: Kandom(n at adress: 0x1d3adffb50)
1: Kandom(n at adress: 0x1d3adffb80)
2: Kandom(n at adress: 0x1d3adffbb0)
3: Kandom(n at adress: 0x1d3adffbe0)
4: Kandom(n at adress: 0x1d3adffc10)

2:包含多个递归调用的递归

在需要将一项工作不断分为两项较小、类似的工作,递归很有用。递归方法有时被称为分而治之策略,比如对标尺找到两端,找到终点并标记,然后同样的操作给标尺的左右两部分继续用。

见如下程序:

#include <iostream>

using namespace std;
const int Len = 66;
const int Divs = 6;
void subdivide(char ar[], int low, int high,int levels);

int main(void)
{
    char ruler[Len];//字符数组
    for(int i = 0; i < Len; i++)
        ruler[i] = ' ';//清空数组
    
    int min = 0; //数组边界开始
    int max = Len - 2; //数组边界结束
    ruler[Len-1] = '\0';
    ruler[min] = ruler[max] = '|';

    for(int i = 1; i < Divs; i++)
    {
        subdivide(ruler,min,max,i);//这样使用递归好像就可以了
        cout << ruler << endl;
    }

    return 0;
}
void subdivide(char ar[], int low, int high,int levels)//levels是为了控制递归退出
{
    if(levels == 0)
        return;

    int mid = (low + high) / 2;
    ar[mid] = '|';

    subdivide(ar,low,mid,levels-1);
    subdivide(ar,mid,high,levels-1);
}

结果:

|                               |                               |
|               |               |               |               |
|       |       |       |       |       |       |       |       |
|   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
 

*

1)刚开始是两边两条斜杠,第二次变成三条,第三次变成5条,一直递归下去,

2)将递归次数写进了形参,然后形成判断条件

十、函数指针

与数据项相似,函数也有地址。函数的地址是存储机器语言代码的内存的开始地址。我们可以编写一个将另外一个函数的地址作为参数的函数,这样第一个函数能找到第二个函数并运行,特殊的是,它允许在不同的时间传递不同函数的地址,这意味着可以在不同的时间使用不同的函数

1:函数指针基础知识

比如我们要设计一个estimate()函数来估算指定行数的代码需要的时间,并希望不同的此恒许愿都将使用该函数。我们将程序员要使用的算法函数地址传递给esitmate(),为此必须:

·获取函数地址

·声明一个指针

·使用函数指针调用函数

1)获取函数地址

使用函数名即可,不用参数,比如说think()是函数,则think是该函数的地址

2)声明函数指针

比如:函数如下

double pan(int);则指针如下

double (*pf)(int);

写的时候直接把原型中的函数名用(*Name)替换了就行了

*一定要加(),不然就是函数名

double (*pf)(int);这时函数指针

double *pf(int),这就是一个函数了

                                                                

3)使用指针调用函数

现在(*pf)扮演的角色就与函数名相同,因此使用(*pf)时,可将它看作是函数名即可!

例如:
double pam(int);

double (*pf)(int);

pf = pam;

2:函数指针示例

下面的程序演示如何使用函数指针,两次调用estimate()函数,依次传递besty()函数的地址,另一次传递pam()函数的地址。如下
 

#include <iostream>

using namespace std;
double Rick(int lines);
double Jack(int lines);
void estimate(int lines, double(*pf)(int));

int main(void)
{
    int code;

    cout << "How many lines of code do you need?\n";
    cin >> code;

    cout << "Here is Rick's estimate: " << endl;
    estimate(code,Rick);

    cout << "Here is Jack's estimate: " << endl;
    estimate(code,Jack);
    
    return 0;
}
double Rick(int lines)
{
    return lines * 0.05;
}

double Jack(int lines)
{
    return (0.03 * lines) + (0.0004 * lines * lines);
}

void estimate(int lines, double(*pf)(int))
{
    cout << lines << " Lines code will take " << (*pf)(lines) << " hours." << endl;
}

结果:

How many lines of code do you need?
100
Here is Rick's estimate:
100 Lines code will take 5 hours.
Here is Jack's estimate:
100 Lines code will take 7 hours.

*

可以看到在最后一行代码我们声明了和Rick,Jack类型相同的函数指针*pf,使用的时候直接用名字参数即可: (*pf)(lines) << " hours." << endl;

3:深入讨论函数指针

看下例:

#include <iostream>

using namespace std;
const double *f1(const double *ar,int n);
const double *f2(const double ar[],int n);
const double *f3(const double ar[],int n);

int main(void)
{
    double av[3] = {1112.3,1542.6,2227.9};
    
    //part1: p1是指向函数的指针
    const double *(*p1)(const double *,int) = f1;//第一种方法,使用传统方法进行给指针赋值
    
    auto p2 = f2; //第二种方法:使用auto方法,让编译器自动判断p2的类型
    
    cout << "PART1:--------------------" << endl;
    cout << "Adress        Value" << endl;
    cout << (*p1)(av,3) << ": " << *((*p1)(av,3)) << endl; //(*p1)(av,3) == f1(av,3),第二个是把该函数内容再显示出来

    cout << p2(av,3) << ": " << *p2(av,3) << endl;//第二种方法,直接把指针名称写出来就行了

    //part2:  p2是由指针构成的数组
    const double *(*pa[3])(const double *,int) = {f1,f2,f3};//指针数组的内容是函数,注意它的声明方法
    
    auto pb = pa;//这样pb也是函数指针数组

    cout << "PART2:--------------------" << endl;
    cout << "Adress        Value" << endl;
    for(int i = 0;i < 3; i++)
        cout << pa[i](av,3) << ": " << *pa[i](av,3) << endl;//pa[1]就是f1了,加*就是它里面的内容
    for (int i = 0; i < 3; i++)
    {
        cout << pb[i](av,3) << ": " << *pb[i](av,3) << endl;
    }

    //part3: 
    //(pc)pd 是指针,指向了一个由函数指针构成的数组
    auto pc = &pa;//pc是指向由三个函数指针作为元素的数组pa的指针
    const double *(*(*pd)[3])(const double *,int) = &pa;//传统方法,首先pd是指针,就是*pd,然后*pd是3个元素数组指针,就是*(*pd)[3],最后每个元素都是函数指针,就有了这个

    cout << "PART3:--------------------" << endl;
    cout << "Adress        Value" << endl;
    cout << (*pc)[0](av,3) << ": " << *(*pc)[0](av,3) << endl;
    
    const double *pdb = (*pd)[1](av,3);
    cout << pdb << ": " << *pdb << endl;

    cout << (*pd)[2](av,3) << ": " << *(*pd)[2](av,3) << endl;
    cout << (*(*pd)[2])(av,3) << ": " << *(*(*pd)[2])(av,3) << endl;//这两种写法其实是一样的

    return 0;
}
const double *f1(const double *ar,int n)
{
    return ar;
}
const double *f2(const double ar[],int n)
{
    return ar + 1;
}
const double *f3(const double ar[],int n)
{
    return ar + 2;
}

结果:
PART1:--------------------
Adress        Value
0xf0e7dff6a0: 1112.3
0xf0e7dff6a8: 1542.6
PART2:--------------------
Adress        Value
0xf0e7dff6a0: 1112.3
0xf0e7dff6a8: 1542.6
0xf0e7dff6b0: 2227.9
0xf0e7dff6a0: 1112.3
0xf0e7dff6a8: 1542.6
0xf0e7dff6b0: 2227.9
PART3:--------------------
Adress        Value
0xf0e7dff6a0: 1112.3
0xf0e7dff6a8: 1542.6
0xf0e7dff6b0: 2227.9
0xf0e7dff6b0: 2227.9

*总结一下

1)

(*pa[2])(av,3) ≠ *pa[2](av,3),括号优先级不一样

pa[2](av,3)来说,pa[2]是指针数组第三个元素,就是函数指针,最后*,就是第三个函数的值

(*pa[2])(av,3)来说,(*pa[2])就是第三个元素函数指针所指向的函数指针,然后赋值,出来的还是地址

2)

对于函数指针来说*pf和pf其实是等价的

3)

请注意pa和&pa的区别(pa是数组),pa是数组第一个元素的地址,即&pa[0],而&pa是整个数组(三个指针)的地址,从数字上说它们相同,但是它们类型不同。

比如pa+1是下一个元素的地址,而&pa+1是后面12个字节内存块的地址。

还有个差别,要得到第一个元素的值,只需要对pa解除即可,而&pa解除两次引用

**&pa == *pa == pa[0]

4)

用auto解放了复杂的声明过程

const double *(*pa[3])(const double *,int) = {f1,f2,f3};
    
auto pb = pa;//这样pb也是函数指针数组

5)

还可以用typedef进行简化(并没有定义新的东西,而是起了一个别名,第五章说过)

比如:typedef int n,这里就把n变成int类型了

比如:typedef double real;这里real就变成了double类型了,而不是变量

实例如下:

typedef const double *(*p_fun) (const double *,int),可以直接用p_fun类型了

p_fun p1 = f1;

p_fun pa[3] ={f1,f2,f3};

十一、复习题和编程练习

1:复习题:

1:使用函数的三个步骤?

定义,调用,声明

2:请创建与下面的描述匹配的函数原型

a:igor()没参数,没返回值

void igor(void);

b:tofu()接收一个int参数,且返回一个float

float tofu(int)

c:mpg()接收两个double,返回一个double

double mpg(double,double)

d:summation()将long数组名和数组长度作为参数,返回一个long

long arr[n];

long summation(long [],int)

e:doctor()接受一个字符串参数(不能修改),并返回double

double doctor(const char *str)

f:ofcourse()将boss结构作为参数,无返回值

void ofcourse(boss bs)  形参名可写可不写

g:plot将map结构的指针作为参数,返回字符串

char *plot(map *pt)

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

void Set(int ar[], int n, int x)

{

     for(i = 0;i<n;i++)

                 {

                              ar[i] = x;

                }

}

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

void Set(int *begin,int *end , int x)
{
    for (int *pt = begin; pt != end ; pt ++ )
    {
        *pt = x;
    }
}

*注意循环条件

5:编写一个double数组名和数组长度作为参数,并返回该数组中最大值的函数。该函数不应该修改数组内容。

double Set(const double ar[],int len)
{
    double max = arr[0];
    for(int i = 1; i < size; i++)
    {
        if(max < arr[i]);
            max = arr[i];
    }
    return max;
}

6:为什么不对基本类型的函数参数使用const?

我们的C++一般是按值传递参数,而不像数组和结构这种,整体复制,修改副本其实对原来的值不影响,但是如果用指针了就必须加const

7:C++使用哪三种C风格的字符串

char数组。char ch[] = "Hello";

字符串。"hello;";

可以指向字符串首字符的指针。char * pt = "Hello.指的是地址

8:编写一个函数,原型如下:

该函数将字符串中所有的c1都替换为c2,并返回替换次数

int replace(char * str, char c1,char c2)

int replace(char * str,char c1,char c2)
{
    int count;
    while(*str)
    {
        if(*str == c1)
        {
            *str = c2;
            count++;
        }
        str++;
    }
    return count;
}

9:表达式*"pizza"代表:字符串首地址,取出p

"taco[2]"呢,taco是数组,第三个元素,双引号就是首地址

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

地址传递不能保护原始数据;但是按值传递内存消耗大

11:函数judge()返回类型是int,它将这样一个函数地址作为参数:将const char指针作为参数,并返回int值,写该函数原型

int judge(int (*pf) (const char));

12:结构如下:

struct applicant

{

        char name[30];

        int ratings[3];

}

a:编写一个显示该结构体内容的函数,参数就是结构体

void show(applicant ap)
{
    for (int  i = 0; i < 2; i++)
    {
        cout << ap.name[i] << ap.ratings[i] << endl;
    }
    
}

b.将该结构的地址作为参数,显示参数所指向结构的内容

void show(applicant *ap)
{
    for (int  i = 0; i < 2; i++)
    {
        cout << ap->name << ap->ratings[i] << endl;
    }   
}

13:假设f1()和f2()原型如下:

void f1(applicant * a);

const char * f2(const applicant * a1,const applicant * a2);

1)请将p1和p2分别声明为指向f1和f2的指针;

2)将ap声明为数组,它包含5个类型与p1相同的指针

3)将pa声明为一个指针,它指向的数组包含10个类型与p2相同的指针

使用typedef方法来实现。

1)

typedef void *(pf_1)(applicant * a);//使用typedef直接变成类型
pf_1 p1 = f1;

typedef char const char *(*p_f2)(const applicant *a1,const applicant *a2);
p_f2 p2 = f2;

2)pf_1 ap[5];

3p_f2 (*pa) [10];

2:编程练习

猜你喜欢

转载自blog.csdn.net/leikang111/article/details/125288203