c++学习笔记——函数

创建自己的函数时,必须理清3个方面——定义、提供原型以及调用。

#include <iostream>
void simple();

int main()
{
    using namespace std;
    cout << "main() will call the simple () function:\n";
    simple();
    //执行函数simple()时,将停止执行main()函数中的代码,等函数simple()执行完毕后,再继续执行main()函数中的代码
    cout << "main() is finished with the simple () function.\n";
    return 0;
}
void simple()
{
    using namespace std;
    cout << " I'm but a simple function.\n";
}
main() will call the simple () function:
 I'm but a simple function.
main() is finished with the simple () function.

定义函数

定义格式略,与c语言相仿,这里需要注意几个点:

(1)对于有返回值的函数,必须使用返回语句,返回值本身可以是变量、常量,或者表达式,但是类型必须是函数头声明的类型或者可以转化成其类型。

(2)c++对于返回值的类型有一定的限制,不能时数组,但可以是其他任何类型——整数、浮点数、指针,甚至可能是结构和对象。(不能直接返回数组,但是可以将数组作为结构对象或者对象组成部分来返回)。

(3)函数在执行返回语句后结束。如果函数包含多条返回语句(例如,它们位于不同的ifelse选项中),则函数在执行遇到的第一条返回语句后结束。

函数原型

#include <iostream>
void cheers(int);
//需要函数原型的原因:能提高效率,
//如果没有函数原型,在主函数中使用它时,编译器会搜索文件剩余部分时将必须停止对main()函数的编译。
//函数原型是一条语句,必须以分号结尾,函数原型不要求提供变量名(可以包括,也可以不包括),有类型列表即可,
double cube(double x);
//原型中的变量名相当于占位符,因此不必和函数定义中的变量名相同。

int main()
{
    using namespace std;
    cheers (5);
    cout << "Give me a number: ";
    double side;
    cin >> side;
    double volume = cube(side);
    //由于cube()有返回值,因此main()可以将其用在赋值语句中。
    cout << "A " << side << " -foot cube has a volume of ";
    cout << volume << " cubic feet.\n";
    cheers(cube(2));//在c++中可以自动将被传递的参数强制转化成期望的类型
    //(当然前提是两者的转化是有意义的,例如这里就不能转化成指针或者结构)。这里2传递后被转化成2.0
    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;
}
 Cheers! Cheers! Cheers! Cheers! Cheers!
Give me a number: 8
A 8 -foot cube has a volume of 512 cubic feet.
 Cheers! Cheers! Cheers! Cheers! Cheers! Cheers! Cheers! Cheers!

c++函数原型和ANSI原型

(1)在c++中函数原型必不可少(在ANSI C中是可选的)

(2)在c++中,括号为空与在括号中使用关键字void是等效的(也就是函数没有参数),在ANSI  C中,括号为空意味着不支持参数(也就是会在后面定义参数列表,不是没有参数)。

在c++中,函数原型不指定参数列表时应使用省略号。例如,void say_bye(…);但一般仅当与接受可变参数的C函数(如printf())交互时才需要这样做。

原型的功能

(1)编译器正确处理函数返回值

(2)编译器检查使用的参数数目是否正确

(3)编译器检查使用的参数类型是否正确,如果正确,这转化成正确的类型。

当传递的参数与函数原型中规定的类型不匹配时,在c中,会导致一些错误,例如如果函数需要的是一个int值(16位),但是程序员传递了一个double值(64位),则程序只检查64中的前16位,并将它们解释成一个int值。但c++可以自动将传递的值转化成原型中指定的类型,条件是两个都输算术类型。

但是自动类型转化不能避免所有的错误,例如,将8.33E27传递给一个int类型的函数就不行,因为这样大的值不能正确转化成int类型,当较大的值转化成较小的类型时,有些编译器就会发出警告,指出会丢失数据。

并且仅当有意义时,才会数据转化,例如,原型不会将整数转化成结构或者指针。

函数参数和按值传递

用于接收传递值的变量被称为形参,传递给函数的值被称为实参。

c++使用参数来表示实参,使用参量来表示形参。

#include <iostream>
using namespace std;
void n_chars(char, int);
//原型中的变量名不必与定义中的变量名相同,并且可以省略。
//另外,就算函数两个参数的类型相同,则必须分别指定每个参数的类型,
//不能组合在一起,例如viod fufu(float a, float b)不能写成viod fufu(float a, b)
int main()
{
    int times;
    char ch;

    cout << "Enter a character: ";
    cin >> ch;//这里使用cin,而不是cin.get(ch)或者ch = cin.get()来读取一个字符,
    //原因是cin>>可以跳过所有的空格和换行符,若使用cin.get()的话,当用于在此处键入字母并且按下enter(会生成一个换行符)时,
    //当下面第18行输入一个数字时,cin.get()会读取这个换行符,当然可以使用编程的方法避免,但是最简单的还是使用cin>>。
    while (ch != 'q')
    {
        cout << "Enter an integer: ";
        cin >> times;
        n_chars(ch, times);//按值传递,这里函数将创建变量c和n,并将传递ch和times的值赋值给c和n,使用的是变量ch和times的副本,不是原始的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";

}

void n_chars(char c, int n)//函数可以有多个参数,在调用函数时,只需使用逗号隔开
{
    while (n-- > 0)
        cout << c;
    
}
Enter a character: w
Enter an integer: 50
wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww
Enter another character or press the q-key to quit: a
Enter an integer: 20
aaaaaaaaaaaaaaaaaaaa
Enter another character or press the q-key to quit: q
The value of times is 20.
Bye
// lotto.cpp -- probability of winning
#include <iostream>
// Note: some implementations require double instead of long double
long double probability(unsigned numbers, unsigned picks);//unsigned int 简称unsigned
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;
}

// the following function calculates the probability of picking picks
// numbers correctly from numbers choices
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;
}//probability函数使用了两种局部变量(result、p和n),形参(numbers和picks)
//形参和局部变量的区别就是形参就是从调用probability()函数那里获得自己的值,而其他变量时从函数中获得自己的值。
Enter the total number of choices on the game card and
the number of picks allowed:
50 6
You have one chance in 1.58907e+07 of winning.
Next two numbers (q to quit): q
bye

函数和一维数组

函数和数组的结合,只需要使用循环将所有的数组元素积累起来,这是一个很常见的任务。

#include <iostream>
const int ArSizes = 8;
int sum_arr(int arr[], int n);//或者int sum_arr(int * arr, int n)
//在c++中,当且仅当用于函数头和函数原型时,int arr[]和int * arr含义一致,都意味着arr是一个指针。
//但是使用数组表示法(int arr[])可提醒用户,arr不仅指向int,还指向int数组的第一个int,
//所以当需要指针指向第一个元素时,使用数组表示法,(当然也可以使用指针表示法,但为了区别第7行的内容,一般使用数组表示法)
//当需要指针指向独立的一个值时,使用指针表示法,
int main()
{
    using namespace std;
    int cookies[ArSizes] = {1, 2, 4, 8, 16, 32, 64, 128};

    int sum = sum_arr(cookies, ArSizes);
    //将数组名作为参数传递给函数,数组名是数组第一个元素的地址,因此此时函数传递的地址,cookies的类型是int *,
    //因此将上面的函数原型替换成int sum_arr(int * arr, ArSize)也对,
    //在c++中,当且仅当用于函数头和函数原型时,int arr[]和int * arr含义一致
    cout << "Total cookies eaten: " << sum << "\n";
    cout << sizeof(cookies);//使用sizeof()函数将得到整个数组的长度,这里是4×8=32,以字节为单位
    cout << &cookies << endl;//数组名cookies是数组第一个元素的地址,但对数组名运用取地址符返回的是整个数组的地址,这里返回的就是一个32字节内存块的地址。
    cout << &cookies[0];//数组第一个元素的地址,注意区分上行。
    return 0;
}

int sum_arr(int arr[], int n)
//为了使函数通用,此处不限于特定长度的数组,方括号指出arr[]这是一个数组,arr是数组名,是一个指针
{
    int total = 0;
    for (int i = 0; i < n; i++)
        total = total + arr[i];
    return total;
}
Total cookies eaten: 255
320x61fdf0
0x61fdf0

将数组作为参数的意义

 好处:(1)将数组地址作为参数可以节省复制整个数组所需的时间和内存。如果数组很大,则使用的拷贝的系统开销很大,程序不仅需要更多的计算机内存,还需要花费时间来复制大块的数据。

坏处:(1)在c中,使用原始数据会增加破坏数据的风险,但是在c++中使用const限定符可以解决这种问题。

// arrfun2.cpp -- functions with an array argument
#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";
    //sizeof(cookies)是整个数组的长度。
    int sum = sum_arr(cookies, ArSize);
    std::cout << "Total cookies eaten: " << sum <<  std::endl;
    sum = sum_arr(cookies, 3);        
    //由于sum_arr只能通过第二个参数来确定数组的长塑,所以可以“欺骗”,这第二个参数是3,意味着此处只计算数组前三个元素的和
    std::cout << "First three eaters ate " << sum << " cookies.\n";
    sum = sum_arr(cookies + 4, 4);    // another lie
    //注意此处在sum_arr函数内部输出的地址不同,输出的是数组第五个元素的地址。
    std::cout << "Last four eaters ate " << sum << " cookies.\n";
    // std::cin.get();
    int b = 7;
    int * p = &b;
    std::cout << sizeof(p) << std::endl;
    std::cout << sizeof(b) << std::endl;

	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";
    //sizeof(arr)其中arr是指针变量,在64位操作系统使用8个字节来存储地址
    //因为在该函数arr只是一个指针变量,本身没有指出数组的长度,所以在必须显式的传递数组的长度。
    for (int i = 0; i < n; i++)
        total = total + arr[i];
    return total; 
}

    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; 
}
0x61fdf0 = array address, 32 = sizeof cookies
0x61fdf0 = arr, 8 = sizeof arr
Total cookies eaten: 255
0x61fdf0 = arr, 8 = sizeof arr
First three eaters ate 7 cookies.
0x61fe00 = arr, 8 = sizeof arr
Last four eaters ate 240 cookies.
8
4
// arrfun3.cpp -- array functions and const
#include <iostream>
const int Max = 5;

// function prototypes
int fill_array(double ar[], int limit);
void show_array(const double ar[], int n);  // don't change data
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 revaluation factor: ";
        double factor;
        while (!(cin >> factor))    // bad input
        {
            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)    // bad input
        {
            cin.clear();
            //cin.clear函数的作用清除cin流错误状态,该函数默认的状态微是ios::goodbit(就是把"坏的"变成"好的")
            //同理,ci.clear(ios::failbit)清除cin流,并设置failbit状态位(就是把“好的”变成“坏的”)。
            while (cin.get() != '\n')
                continue;// 把错误的输入字符用cin.get()清出输入流,从新再来接受
           cout << "Bad input; input process terminated.\n";
           break;
        }
        else if (temp < 0)     // signal to terminate
            break;
        ar[i] = temp;
    }
    return i;
}

// the following function can use, but not alter,
// the array whose address is ar
void show_array(const double ar[], int n)
//c++函数使用普通参数时,按照c++按值传递数据,函数使用的是数据副本,可以对数据实现自动保护。
//但接收数组名的函数将使用原始数据,上面的fill_array()就是直接接收的数据名,能够修改原始数据,
//这里show_array()函数在声明形参时使用关键字const(),该声明表明了指针ar指向的是常量数据,这就意味着不能是用ar修改数据。
//这里需要注意的是:并不意味着原始数据必须是常量,只是意味着不能在show_array()函数中使用ar来修改数据。
//show_array()将数组视为只读数据。
{
    using namespace std;
    for (int i = 0; i < n; i++)
    {
        cout << "Property #" << (i + 1) << ": $";
        cout << ar[i] << endl;
    }
}

// multiplies each element of ar[] by r
void revalue(double r, double ar[], int n)
{
    for (int i = 0; i < n; i++)
        ar[i] *= r;
}
Enter value #1: 10000
Enter value #2: 52220
Enter value #3: 89552
Enter value #4: 25475
Enter value #5: 55466 
Property #1: $10000
Property #2: $52220
Property #3: $89552
Property #4: $25475
Property #5: $55466
Enter revaluation factor: 0.9
Property #1: $9000
Property #2: $46998
Property #3: $80596.8
Property #4: $22927.5
Property #5: $49919.4
Done.

使用数组区间的函数

除了使用将指向数组起始处的指针作为第一个参数,把数组的长度作为第二个参数以外,还可以指定元素区间,这可以通过两个指针来完成,第一个指针标识数据的开头,另一个标识数组的结尾。标识数据结尾的参数是指向最后一个元素后面的指针。

// arrfun4.cpp -- functions with an array range
#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};
//  some systems require preceding int with static to
//  enable array initialization
    int sum = sum_arr(cookies, cookies + ArSize)
    //这里计算大的数组元素的总和,因为最后一个元素的指针是cookies+ArSize-1,那么后面的那个元素的指针是cookies+ArSize
    cout << "Total cookies eaten: " << sum <<  endl;
    sum = sum_arr(cookies, cookies + 3);        // first 3 elements
    //这里计算的是数组前3个元素的总和,计算到cookies+3-1的元素。
    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; 
}
Total cookies eaten: 255
First three eaters ate 7 cookies.
Last four eaters ate 240 cookies.

指针和const

有两种方式可以将const关键字用于指针:(1)让指针指向一个常量对象,这样可以防止使用指针来修改所指向的值 (2)将指针本身声明成常量,这样可以防止改变指针指向的位置。

一级间接关系时:指针赋值的情况有(1)常规变量的地址赋值给常规指针 (2)const变量的地址赋值给指向const的指针(3) 将常规变量赋值地址给指向const的指针(例如下面代码)

注意:不能将const变量的地址赋值给常规指针;

综上:数据类型本身不是指针,则可以将const数据或者非const数据的地址赋值给指向const的指针,但是const数据的地址禁止赋给非const指针。

数组同理,不能将常量数组的地址赋值给非常量指针。

#include <iostream>
int main()
{
    using namespace std;
    int age = 30;
    const int * pt = &age;
    //这里需要注意的是:pt声明并不意味着它指向的值是一个常量,只是对于pt而言,这个值是常量。
    //*pt += 1;
    //这里不合法,pt指向age,而age不是const,可以直接直接通过age来修改age的值,但是不能使用pt指针来修改它。
    //cin >> *pt;
    
    age = 20;//合法
    //*pt = 20;//error

    const float g_earth = 9.80;
    const float * pe = &g_earth;
    //这种情况就是把const变量的地址赋值给指向const的指针,这种情况下,不能使用g_earth来修改其值,也不能使用pe来修改其值。
     
    const float g_moon = 1.63;
    //float * pm = &g_moon;
    //这种情况就是把const变量的地址赋值给常规指针,c++禁止这样做,
    //原因是如果将g_moon的地址赋值给pm,则可以使用pm来修改g_moon的值,这违背了const变量的定义。

    int height = 170;
    int * py = &height;
    const int * pl = py;
    //将指针指向指针,此处是一级间接关系,在此处将非const指针(py的值,也就是变量height的地址)赋值给const指针(pl)是允许的。
    cout << *pl;

    //下面讨论二级间接关系时候的状态
    const int **pp2;
    int * p1;
    const int n = 13;
    /*pp2 = &p1;//error
    *pp2 = &n;
    *p1 = 10;*/
    //当指针涉及到二级间接关系时,会出现混乱,这里如果合法,则意味着可以通过修改*pl的值来修改const变量n的值


    return 0;
}

使用const的好处

(1)将指针参数声明为指向常量数据的指针理由有二:第一,这样可避免由于无意间修改数据而导致的编程错误;第二,使用const使得函数能够处理const和非const实参,否则将只能接收非const数据。

(2)应该尽可能将形参声明为指向const的指针。

int sloth = 3;
const int * ps = &sloth;
//不允许通过ps来修改sloth的值,可以将新地址赋值给ps
int * const finger = &sloth;
//此处const关键字的位置不同,这种格式可以使得finger只能指向sloth,但允许finger来修改sloth的值

简而言之,就是finger和*ps都是const,但是ps和*finger不是。

double trouble = 2.58;
const double * const stick = &trouble;
//此处声明了一个指向const对象的const指针,其中stick只指向trouble,并且stick不能用来修改trouble的值,
//简言之,就是stick和*stick都是const。

函数和二维数组

与一维数组一样,编写二维数组作为参数的函数,数组名依旧被视为是指针,相应的形参是一个指针,声明这个指针案例如下:

#include <iostream>
int sum(int (*ar2)[4], int size);
//这里的括号时不能少的,少了的话为int sum(int *ar2[4], int size)声明的是一个由4个指向int的指针组成的数组(指针组成的数组)
//并非是一个由4个int组成的数组的指针(数组的指针)
//或者是另一种形式
//int sum(int ar2[][4], int size);
int main()
{
    using namespace std;
    int data [3][4] = {{1,2,3,4}, {9,8,7,6}, {2,4,6,8}};//三行四列
    int total = sum(data, 3);//这里是3是因为data是一个数组,它有三个元素,一个元素是一个数组,由4个int值组成。
    cout << total << endl;
    return 0;
}


//ar2是指针不是数组,它指向的是4个int组成的数组,所以两种方式都指定了列数(4),
//故没有把列数作为独立的函数参数进行传递,由于指定了列数,所以sum函数只接受4列组成的数组。
 int sum(int ar2[][4], int size)
{
    int total = 0;
    for(int r = 0; r < size; r++)
        for(int c = 0; c < 4; c++)
            total += ar2[r][c];//或者是*(*(ar2 + r) + c)
    return total;
 }
60

函数和C风格字符串

将字符串作为参数传递给函数,表示字符串的方式有3种。

(1)char数组;例如:char ghost[15] = "galumphing";

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

(3)被设置为字符串常量的地址的char指针;例如char * str = “galumphing”;

上面的这三种类型都是char指针(准确来说是char*),所以可以将其作为字符串处理函数的参数。将字符串作为参数来传递,但实际上传递的是字符串的第一个字符的地址。这就意味着字符串函数原型应将其表示为字符串的形参声明成为char*类型。

:字符串有内置的结束字符(末尾的空值字符),但是不以空值字符结尾的char数组只是数组,而不是字符串。这意味着不必将字符串长度作为参数传递给函数,而函数可以使用循环依次检查字符串中的每个字符,直到遇到末尾的空字符为止。

将c风格字符串作为参数的函数

// strgfun.cpp -- functions with a string argument
#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)
//C_int_str()函数不应修改字符串的内容,编译器将捕获这种错误。
//也可以在函数头使用数组表示法,例如unsigned int c_int_str(const char str[], char ch)
//也就是说这里的形参不一定非得是数组名,也可以是其他形式的指针。
{
    unsigned int count = 0;

    while (*str)        // quit when *str is '\0'
    {
        if (*str == ch)
        //str最初指向的是字符串的第一个字符,因此*str表示的是第一个字符,
            count++;
        str++;        // move pointer to next char
        //表达式str++将指针没增加一个字节,使之指向字符串中的下一个字符,最后当指向结尾的空值字符,使得*str等于0,结束循环。
    }
    return count; 
}
3 m characters in minimum
2 u characters in ululate

返回c风格字符串的函数

注意:函数是无法返回一个字符串的,但是可以返回字符串的地址。

// strgback.cpp -- a function that returns a pointer to char
#include <iostream>
char * buildstr(char c, int n);     // prototype
int main()
{
    using namespace std;
    int times;
    char ch;

    cout << "Enter a character: ";
    cin >> ch;
    cout << "Enter an integer: ";
    cin >> times;
    char *ps = buildstr(ch, times);
    cout << ps << endl;
    delete [] ps;                   // free memory
    //这里必须使用delete释放该字符串占用的内存,因为ps指向的是由new分配的内存
    ps = buildstr('+', 20);         // reuse pointer
    cout << ps << "-DONE-" << ps << endl;
    delete [] ps;                   // free memory
    // cin.get();
    // cin.get();
    return 0;
}

// builds string made of n c characters
char * buildstr(char c, int n)
{
    char * pstr = new char[n + 1];
    //要创建包含n个字符的字符串,需要能够存储n+1个字符的空间,以便存储空值字符。
    pstr[n] = '\0';         // terminate string
    while (n-- > 0)//这里使用的n--,意味先使用这个值然后再递减,所以pstr[0]是最后被设置成c的元素,
    //就相当于是从后往前来填充字符串,这样可以避免使用额外的变量
        pstr[n] = c;        // fill rest of string
    return pstr; 
    //函数结束后,pstr(不是字符串)使用的内存将被释放,但是在此处返回了pstr的值,
    //因此可以在主函数中的指针ps来访问新建的字符串。
}
Enter a character: a
Enter an integer: 5 
aaaaa
++++++++++++++++++++-DONE-++++++++++++++++++++

函数和结构

在涉及函数时,结构将其数据组合成单个实体或者数据对象,该实体被视为一个实体。

(1)可以将结构赋值给另一个结构

(2)可以像普通变量那样按值传递结构,此时使用的是函数的副本

(3)函数可以返回结构,与数组名不一样的是,数组名是数组的第一个元素的地址,但是结构名就仅仅是结构的名称,要想获得结构的地址,就必须使用取地址符&。

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

// travel.cpp -- using structures with functions
#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、t1和t2都是travel_time结构

    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";
}
Two-day total: 10 hours, 40 minutes
Three-day total: 15 hours, 12 minutes

传递结构的地址

传递结构的地址可以节省时间和空间,

// strctptr.cpp -- functions with pointer to structure arguments
#include <iostream>
#include <cmath>

// structure templates
struct polar
{
    double distance;      // distance from origin
    double angle;         // direction from origin
};
struct rect
{
    double x;             // horizontal distance from origin
    double y;             // vertical distance from origin
};

// prototypes
void rect_to_polar(const rect * pxy, polar * pda);
//将形参声明为指向polar的指针,即*polar类型,由于函数不应该修改结构,因此使用了const修饰符
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);        // pass address
        cout << "Next two numbers (q to quit): ";
    }
    cout << "Done.\n";
    return 0;
}

// show polar coordinates, converting angle to degrees
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";
}

// convert rectangular to polar coordinates
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); 
}
Enter the x and y values: 58 40
distance = 70.4557, angle = 34.5923 degrees
Next two numbers (q to quit):

  虽然C风格字符串和string对象的用途几乎相同,但是与数组相比,string对象和结构更相似,比如说可以将结构作为完整的实体传递给函数,也可以将一个对象作为完整的实体进行传递,如果需要多个字符串,可以声明一个string对象数组,而不是一个二维char数组。

// topfive.cpp -- handling an array of string objects
#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]);//读取一行字符(字符间有空格),函数的基本格式:getline(cin, str, '结束符')
        //结束符默认是回车符。
    }

    cout << "Your list:\n";
    display(list, SIZE);
    // cin.get();

	return 0; 
}

void display(const string sa[], int n)//形参是sa是一个指向string对象的指针,因此sa[i]是一个string对象
{
    for (int i = 0; i < n; i++)
        cout << i + 1 << ": " << sa[i] << endl;
}
Enter your 5 favorite astronomical sights:
1: M13
2: M14
3: M15
4: M16
5: M16
Your list:
1: M13
2: M14
3: M15
4: M16
5: M16

函数和array数组

类对象是基于结构的,可按值将对象传递给函数,这种情况下,函数处理的是原始对象的副本;当然也可以传递指向对象的指针,这让函数可以操作原始对象。

//arrobj.cpp -- functions with array objects
#include <iostream>
#include <array>
#include <string>
const int Seasons = 4;
const std::array<std::string, Seasons> Snames =
    {"Spring", "Summer", "Fall", "Winter"};
    //此处的const array对象Snames是在所有函数之前声明的,因此可在后面的任何函数中使用它

void fill(std::array<double, Seasons> * pa);//pa是指向array<double, 4> 类型的指针
void show(std::array<double, Seasons> da);
int main()
{
    std::array<double, 4> expenses;//expense的类型是array<double, 4>
    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';
}
Enter Spring expenses: 85
Enter Summer expenses: 98
Enter Fall expenses: 56.2
Enter Winter expenses: 54.1

EXPENSES
Spring: $85
Summer: $98
Fall: $56.2
Winter: $54.1
Total: $293.3

递归

函数可以自己调用自己,但在c++中,main()函数不允许自己调用自己。这种功能被称为递归。

包含一个递归调用的递归

void recurs(argumentlist)
{
    statemental
    if (test)//test是false时,链被断开。
        recurs(arguments)
    statements2;
}

当if语句时true时,每个recurs()调用都将执行statements1,然后再调用recurs(),不会执行statement1,当if语句时false,当前调用将执行statements2。

当前调用结束后,程序将控制权将返回给调用它的recurs(),而recurs将执行其statements2部分,然后结束,并将控制权返回给前一个调用,依此类推。

// recur.cpp -- using recursion
#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 << " (n at " << &n << ")"  << endl;
    if (n > 0)
        countdown(n-1);     // function calls itself
    cout << n << ": Kaboom!" << "( n at " << &n << ")" << endl;
}
Counting down ... 4 (n at 0x61fe00)
Counting down ... 3 (n at 0x61fdd0)
Counting down ... 2 (n at 0x61fda0)
Counting down ... 1 (n at 0x61fd70)
Counting down ... 0 (n at 0x61fd40)
0: Kaboom!( n at 0x61fd40)
1: Kaboom!( n at 0x61fd70)
2: Kaboom!( n at 0x61fda0)
3: Kaboom!( n at 0x61fdd0)
4: Kaboom!( n at 0x61fe00)

注:每个递归调用都创建自己的一套变量,因此当函数到达第5次调用时,将有5个独立的n变量,其中每个变量的值都不同,相同层级地址相同。

包含多个递归调用的递归

在需要将一项工作不断分成两项小的、类似的工作时,递归非常有用。

// 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 = 1; j < Len - 2; j++)
            ruler[j] = ' ';  // reset to blank ruler
    }
    // std::cin.get();

    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); 
}
|                                                               |
|                               |                               |
|               |               |               |               |
|       |       |       |       |       |       |       |       |
|   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||

在该程序中,函数subdivide使用变量level来控制层级。函数调用自身时,把level减去1。当level未零时,该函数将不在调用自己。subdivide()调用自己两次,一次是针对左边那部分,一次是右边。最初的中点被用作一次调用的右端点和另一次调用的左端点。调用次数乘几何级数增长。

函数指针

函数也有地址,函数的地址是存储在其机器语言代码的内存的开始地址。利用这个可以编写将另一个函数的地址作为参数的函数。这样第一个函数将能够找到第二个函数,并且运行它。与直接调用另一个函数相比,它允许在不同的时间传递不同函数的地址,这意味着可以在不同的时间使用不同的函数。

函数指针的基础知识

获取函数的地址:函数名就是其地址,例如think()这个函数,think就是该函数的地址。要将函数作为参数进行传递,必须传递函数名,注意:要区分传递的是函数的地址还是返回值,

process(think())//传递的是函数的返回值
thought(think)//传递的是函数的地址

声明函数指针

声明指向某种类型的指针时,必须指定指针指向的类型。故声明指向函数的指针时,也必须指定指针指向的函数类型。也就是声明应指定函数的返回类型以及函数的特征标(参数列表)。  也就是说,声明应像函数那样指出有关函数的信息。例:

double pam(int)//函数原型
double (*pf) (int)//这里与pam()声明类似,这里将pam替换成(*pf),
由于pam是函数,所以(*pf)也是函数,pf是函数指针,
技巧就是要声明指向特定类型的指针,可以首先编写这种函数的原型,
比如第一行代码,再用(*pf)代替函数名,这样pf就是这类函数的指针。

这里回顾之前的知识点:使用括号将*pf括起来是因为括号的优先级比*要高,所以*pf(int)意味着pf()是一个返回指针的函数,而(*pf)(int)意味着pf是一个指向函数的指针。

double pam(int);
double (*pf) (int);//这里声明了一个和第一行函数原型类型一致的函数指针,
//pf指向的函数接受一个int参数,并返回是double值的函数
pf = pam;//将相关的函数的地址赋值给它,
//另外需要注意pam的特征标必须和pf一致,否则编译器拒绝这种赋值

使用指针来调用函数

(*pf)扮演的角色与函数名相同,把它看做是函数名即可。

double pam(int);
double (*pf) (int);
pf = pam//pf现在也指向pam函数
double x = pam(4);
double y = (*pf) (5);//使用函数指针来调用函数
double y =  pf(5)//也可以像使用函数名那样使用pf

pf和(*pf)等价的原因:

两种学派:

(1)pf是函数指针,而*pf是函数,所以可以使用(*pf)( )用作函数调用;

(2)函数名是指向该函数的指针,指向函数的指针的行为应与函数名相似,故可以使用pf()用作函数调用使用。

函数指针的深入了解

在函数原型中,参数列表const double ar[]和const double*ar含义完全相同,在函数原型中,可省略标识符,可以将上面两者简化成const double[]和const double *,但是函数定义时必须提供标识符,也就是必须使用const double ar[]或者const double *ar。

下面是比较复杂的函数原型和其指针的表示

函数原型:const double * f1(const double ar[], int n);//该函数原型表明返回值是const double *类型。

对应的函数指针声明:const double * (*p1) (const double *, int ) = f1;//要声明其指针,假定使用的指针名为p1,只需使用(*p1)替换f1即可。这里还同时对其进行了初始化。

同时声明多个函数指针,形式为函数指针数组:const double * (*pa[3]) (const double *, int) = {f1, f2, f3};//[3]放置位置的说明:首先pa是一个包含3个元素的数组,所以是pa[3],运算符[]的优先级高于*,所以*pa[3]表明是一个包含三个指针的数组。然后其他部分,特征标为const double *,int,返回值类型是const double *。所以pa是一个包含三个指针的数组,其中每个指针都指向以const double *和int作为参数,并且返回const double*的函数。

声明完相关数组后如何调用:const double *px = pa[1](av, 3);或const double *py = (*pa[1])(av, 3);

关于auto

使用c++11的自动类型判断

例如:

auto p2 = f1;

c++会声明自动类型的变量,根据被赋值的类型自动推导变量的类型

但自动类型推断只能用于单值初始化,不能用于初始化列表(比如上面函数指针数组)但是可以通过上面的pa来声明同类型的指针。pb与pa同类型,都是包含三个函数指针的数组。

auto pb = pa;

使用typedof进行简化

创建类型别名:

typedef double real;//使real成为double的别名

同样可以使用其简化指针类型的声明

typedef const double * (*p_fun) (const double *, int )
p_fun p1 = f1;//p1指向f1函数
p_fun pa[3] = {f1, f2, f3};//pa是一个包含三个函数指针的数组
p_fun (*pd)[3] = &pa;//指针pd指向pa

这里将别名作为标识符进行声明,并在开头使用关键字typedef。可以将p_fun声明为函数指针类型的别名。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值