# C++基础7-函数1

本文是《C++ Primer Plus》的笔记,本文中的案例均自己实践过,如需转发请在转发开头贴上原文地址,谢谢!

# 函数

# 函数的概念

函数可以想象成建筑施工中的一个门,一扇窗,门有门把手和门板,窗有玻璃和木框,相应的它在程序上一个逻辑上的模块,从外部来说,关注一个函数会关注它的输入输出,从内部来说,便是这个函数的内部处理细节。在C++中,它自带一个包含函数的大型库(标准ANSI库加多个C++类),但是如果想要更高效率地编程,还需要深入学习STL和BOOST C++两个大库。

# 函数的定义

returnType functionName(parameterList){
    语句块;
    return;
}
1
2
3
4

C++的返回值类型不能是数组,除此之外可以是任何类型:整数,浮点数,指针,甚至是结构和对象!

# 函数原型和调用

函数原型一般是函数头去掉语句块,后面添加分号。

为什么需要原型呢?原型描述了函数到编译器的接口,将函数返回值的类型以及参数的类型和数量告诉编译器。如果没有原型,编译器在编译的时候需要在文件中查找,这样效率很低,当函数不存在文件中时这将也是一个大问题。避免使用原型唯一方法就是在首次使用函数之前定义它,但C++的编程风格是main()放在最前面,因为它通常提供了程序的整体结构。

long double calcSeries(int n);

int main(int argc,char *argv[])
{
    cout << calcSeries(3) << endl;
}

long double calcSeries(int n) {
    long double result = 1;
    for (int i = 1; i <= n; i++)
    {
        result *= i;
    }
    return result;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 函数和数组

如前面在指针一节中指出,数组和指针在函数的实参和形参中是等价的。另外数组作为形参时,不要试图使用方括号表示法来传递数组长度:

void test(int arr[10])

看下面的事例:

void printArr(int arr[], int n) {
    for (int i=0;i<n;i++)
    {
        cout << arr[i] << endl;
    }
}

void printArr(int * beginPtr, int * endPtr) {
    while (beginPtr != endPtr) {
        cout << *beginPtr << endl;
        beginPtr++;
    }
}

int main(int argc,char *argv[])
{
    int a[10] = { 1,2,3,4,5,6,7,8,9 };
    printArr(a + 3, 3);
    cout << "------------" << endl;
    printArr(a + 2, a + 8);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

如果希望传入的数组不想改变的话,可以在数组前面加const。

# 函数和二维数组

//int sum(int(*arr)[3], int size) {
//注意下面不是一个数组而是一个指针。
int sum(int arr[][3], int size) {
    int sumResult = 0;
    for (int i = 0; i < size;i++) {
        for (int j = 0; j < 3; j++) {
            sumResult += arr[i][j];
        }
    }
    return sumResult;
}

int main(int argc,char *argv[])
{
    int data[2][3] = {{1,2,3},{4,5,6}};
    cout << sum(data , 2);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 函数和C风格字符串

C风格字符串有三种:

  1. char数组
  2. 用括号括起的字符串常量
  3. 指向字符串的char指针
int printStr(char * str) {
    int sum = 0;
    while (*str++) {
        sum++;
    }
    return sum;
}

char * buildStr(char ch,int n) {
    char * returnPtr = new char[n + 1];
    returnPtr[n] = '\0';
    while (n-- > -1) {
        returnPtr[n] = ch;
    }
    return returnPtr;
}

void printStr(string a1) {
    cout << a1 << endl;
}

int main(int argc,char *argv[])
{
    //char cstr1[5] = "hello";
    //这种情况会报错,因为没有空间在末尾添加'\0'
    char cstr1[6] = "hello";
    cout << strlen(cstr1) << endl;
    cout << sizeof(cstr1)/sizeof(cstr1[0]) << endl;
    char cstr2[5] = { 'h','e','l','l','o' };
    //这种情况就不会报错,但是末尾没有'\0'
    cout << strlen(cstr2) << endl;
    char cstr3[6] = { 'h','e','l','l','o' };
    //如果空间大于初始化字符集个数的话,那么会自动添加'\0'
    cout << strlen(cstr3) << endl;
    cout << printStr(cstr2) << endl;
    cout << printStr(cstr3) << endl;
    string str1 = "hello";
    const char * str1Ptr = "hello";
    //需要在char前面添加const
    cout << strlen(str1Ptr) << endl;
    cout << buildStr('a', 10) << endl;

    char t1[20] = "hello world!";
    printStr(t1);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

c++中string类定义了从char *到string的转换功能,可以使用C风格字符串来初始化string对象。

# 函数和结构


struct personInfo {
    const char * name;
    int age;
};

personInfo * dealpersonInfo(personInfo * ptr1,const char * name) {
    ptr1->name = name;
    ptr1->age = 20;
    return ptr1;
}

void dealPersonRef(personInfo & ptr1, const char * name) {
    ptr1.name = name;
    ptr1.age = 25;
}

int main(int argc,char *argv[])
{
    personInfo p1;
    personInfo * p2 = dealpersonInfo(&p1, "xie");
    cout << p2->name << " " << p2->age << endl;
    dealPersonRef(p1,"xie2");
    cout << p2->name << " " << p2->age << endl;

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

# 函数和string对象


void display(const string list[],int n) {
    for (int i = 0; i < n; i++) {
        cout << list[i] << endl;
    }
}
int main(int argc,char *argv[])
{
    const int SIZE = 3;
    string list[SIZE];
    for (int i = 0; i < SIZE; i++) {
        getline(cin, list[i]);
    }

    display(list, SIZE);
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 函数的递归

递归是通过已知和未知之间的联系来不断推断,直到已知情况再推回结果。比较常见的一个例子:在电影院看电影的时候我们假如我们只能看到前一排,假如我们想知道自己是第几排,那么我们会问前面的人,前面的人如果也不知道的话再问前面的人,直到问到第一排,第一排告诉第二排,这是第一排,第二排告诉第三排这是第二排。。。直到我们自己前面那排告诉我们他是第几排。

在函数中递归的形式:

void recursiveFunc(argumentlist){
    语句块1;
    if(test)
        recursiveFunc(arguments);
    语句块2;
}

实例:

void countLevel(int n) {
    cout << "asking level :" << n << endl;
    if (n > 1) {
        countLevel(n-1);
    }
    cout << "this is :" << n << "st	level stair." << endl;
}

int main(int argc,char *argv[])
{
    countLevel(5);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

函数中的多个递归调用:

void printArr(char * arr, int n) {
    for (int i = 0; i < n; i++) {
        cout << arr[i];
    }
    cout << endl;
}

void divideStr(char arr[],int left,int right) {
    if ((right - left) > 1) {
        int middle = (right - left) / 2 + left;
        if ((right - left) % 2 == 0) {
            arr[middle] = '|';
            divideStr(arr,left,middle);
            divideStr(arr,middle,right);
        }else{
            arr[middle] = arr[middle + 1] = '|';
            divideStr(arr, left, middle);
            divideStr(arr, middle+1,right);
        }
    }
}

void divideStr2(char arr[], int left, int right,int deep) {
    if (deep > 0) {
        int middle = (right - left) / 2 + left;
            deep--;
        if ((right - left) % 2 == 0) {
            arr[middle] = '|';
            divideStr2(arr, left, middle,deep);
            divideStr2(arr, middle, right,deep);
        }
        else {
            arr[middle] = arr[middle + 1] = '|';
            divideStr2(arr, left, middle,deep);
            divideStr2(arr, middle + 1, right,deep);
        }
    }
}


int divideStr3(char arr[], int left, int right, int deep=1) {
    if ((right - left) > 1) {
        int middle = (right - left) / 2 + left;
        deep++;
        if ((right - left) % 2 == 0) {
            divideStr3(arr, left, middle, deep);
            //divideStr3(arr, middle, right, deep);
            return divideStr3(arr, middle, right, deep);
        }
        else {
            divideStr3(arr, left, middle, deep);
            //divideStr3(arr, middle + 1, right, deep);
            return divideStr3(arr, middle + 1, right, deep);
//注意,在visual studio 2017中,将上面的话换成上面注释的行数,不会报错,正常运行,但是如果运行下面的 cout << deep << endl; 则会导致出错。使用其他的编译器不知道会不会有这个问题。
        }
        //cout << deep << endl;
    }
    else {
        return deep;
    }
    //cout << deep << endl;
}

int main(int argc,char *argv[])
{
    const int arrLen = 40;
    char arr[arrLen];
    arr[0] = arr[arrLen - 1] = '|';
    for (int i = 1; i < arrLen-1; i++) {
        arr[i] = ' ';
    }
    //divideStr(arr, 0, arrLen - 1);
    //printArr(arr,arrLen);
    int totalDivide = divideStr3(arr, 0, arrLen - 1);
    cout <<"output:"<< totalDivide << endl;

    for (int i = 0; i < totalDivide; i++) {
        divideStr2(arr, 0, arrLen - 1, i);
        printArr(arr,arrLen);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81

# 函数参数解析

# 例1:

int addTarget(int &n) {
    n += 1;
    return n;
}
int multiplyTarget(int &n) {
    n *= 2;
    return n;
}

int main(int argc,char *argv[])
{
    int intTest = 1;
    cout << multiplyTarget(intTest) << ":" << addTarget(intTest) << endl;
    cout << addTarget(intTest) << ":" << multiplyTarget(intTest) << endl;
}

在c++ 14中得到结果:
4:2
9:8
在c++ 17中得到结果:
2:3
4:8

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

结论:

  1. cout链式计算在c++ 14中是从右往左计算,在c++ 17中是从左往右计算。
  2. 他们都是从左往右输出。

# 例2:

int add(int a, int b, int c)
{
    cout << a << ":" << b << ":" << c << endl;
    return a + b + c;
}

int main(int argc,char *argv[])
{

    auto i = 0;
    cout << add(i++, i, ++i) << endl;
    i = 0;
    cout << add(++i, i, i++) << endl;
}

在c++ 14中得到结果:
1:2:2
5
2:2:0
4

在c++ 17中得到结果:
1:1:1
3
2:1:0
3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

结论:

  1. 函数实参计算在两个标准中都是从右往左计算压栈。
  2. 在c++14中后置自增相当于将变量给一个临时变量,而前置自增相当于引用变量自身。当变量自增时,前置自增依旧会增加。
  3. c++14中是实参全部计算完再压栈,而c++17中是实参计算一个立即压栈。

# 函数指针

指向函数的指针,这部分将融合前面的指针数组,数组指针,函数指针,写一些比较复杂的表达式。

# 例1


int * getInt2(int a[]) {
    (*a) *= 2;
    return a;
}

int main(int argc,char *argv[])
{
    int intInit = 1;
    int * (*intPtr)(int *) = getInt2;
    cout << intInit++ << ":" << intInit << endl;
    cout << *intPtr(&intInit) << ":" << intInit<<endl;
    cout << intInit << endl;
    cout << *intPtr(&intInit) << endl;
    //(* ptr)函数书写
    cout << *(*intPtr)(&intInit) << ":" << *(*intPtr)(&intInit) << endl;
    cout << intInit << endl;
}

在c++ 14中得到结果:
1:2
4:2
4
8
32:16
32

在c++ 17中得到结果:
1:2
4:4
4
8
16:32
32
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

由例1和例2能理解上面的结果,c++17输出的是我原来期望的结果。

# 例2

int * getInt1(int * a) {
    (*a) *= 1;
    return a;
}

int * getInt2(int a[]) {
    (*a) *= 2;
    return a;
}

int * getInt3(int * a) {
    (*a) *= 3;
    return a;
}


int main(int argc,char *argv[])
{

    int intInit = 1;
    int * (*intPtrArr[3])(int *) = {getInt1,getInt2,getInt3};
    //这里不能使用auto,因为自动类型推断只能用于单值初始化,而不能用于初始化列表。
    int * (*(*intPtrArr2)[3])(int *) = &intPtrArr;

    //使用typedefine简化上面的步骤
    typedef int * (*funPtr)(int *);
    funPtr t1[3]= { getInt1,getInt2,getInt3 };
    funPtr (*t2)[3]= &t1;

    typedef int * (*funPtr2[3])(int *);
    funPtr2 * t3 = &t1;
    cout << *(*t3[0])(&intInit) << endl;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33