C++ Primer Plus 学习笔记(函数)(一)

1. 基本知识

使用 C++ 函数,必须完成以下工作:

  • 提供函数定义
  • 提供函数原型
  • 调用函数

库函数是已经定义和编译好的函数,同时可以使用标准库头文件提供其原型,因此只需正确地调用这种函数即可。

1. 1 定义函数

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

void functionName (parameterList)
{
    statement(s)
    return;
}

其中 parameterList 指定了传递给函数的参数类型和数量,可选的返回语句标记了函数的结尾。

有返回值的函数将生成一个值,并将它返回给调用函数。这种函数的类型被声明为返回值的类型,其通用格式如下:

typeName functionName (parameterList)
{
    statement(s)
    return value;
} 

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

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

1. 2 函数原型和调用

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

C++ 允许将一个程序放在多个文件中,单独编译这些文件,然后将它们组合起来。在这种情况下,编译器在编译 main() 时,可能无权访问函数代码。如果函数位于库中,情况也将如此。避免使用函数原型的唯一方法是,在首次使用函数之前定义它,但这并不总是可行的。另外,C++ 的变成风格是将 main() 放在最前面,因为它通常提供了程序的整体结构。

原型确保以下几点:

  • 编译器正确处理函数返回值
  • 编译器检查使用的参数数目是否正确
  • 编译器检车使用的参数类型是否正确,如果不正确则转换为正确的类型

2. 函数参数和按值传递

C++ 通过长安之传递参数,这意味着将数值参数传递给函数,而后者将其赋给一个新的变量。用于接受传递值的参量被称为形参,传递给函数的值被称为实参。出于简化的目的,C++ 标准使用参数(argument)来表示实参,使用参量(parameter)来表示形参,因此参数传递将参数赋给参量。

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

3. 函数和数组

函数头:

int sum_arr(int arr[], int ArSize); // arr = array name, n = size

可以将数组作为函数的形参传入函数。上面语句中,方括号指出 arr 是一个数组,而方括号为空则表明,可以将任何长度的数组传递给该函数。但实际情况并非如此,arr 实际上并不是数组,而是一个指针。在编写函数的其余部分时,可以将 arr 看作是数组。

函数调用如:

int sum = sum_arr(array, size);

对于数组,array 是数组名,根据 C++ 规则,array是其第一个元素的地址,因此函数传递的是地址。由于数组的元素的类型为 int, 因此 array 的类型必须是 int 指针,即 int*。这表明正确的函数头应该是这样的:

int sum_arr(int * arr, int n);  // arr = array name, n = size

其中用 int* arr 替换了int arr[]。这两个函数头都是正确的。因为在 C++ 中,当且仅当用于函数头或者函数原型中,int * arr 和 int arr[] 的含义才是相同的。另外,必须显式地传递数组长度,而不能在函数中使用 sizeof,因为指针本身并没有指出数组长度。

使用 const 保护数组

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

使用普通参数时,不修改原始数据的保护将会自动实现,因为 C++ 按值传递数据,而且函数使用数据的副本。然而接受数组名的函数将使用原始数据。为防止函数无意中修改数组的内容,可以在声明形参时使用关键字 const。

数组区间

一种给函数提供数组信息的方法,即指定元素区间,可以通过传递两个指针来完成:一个指针标识数组的开头,另一个指针表示数组的尾部。
假设有如下声明:

double elboud[20];
begin = elboud;
end = elboud + 20;

则指针 elboud 和 elboud + 20 定义了区间。数组名 elboud 指向第一个元素,表达式 elboud + 19 指向最后一个元素(elboud[19]),因此,elboud + 20 指向数组结尾后面的一个位置。
根据减法规则,end - begin 是一个整数值,等于数组的元素数目。

指针和 const

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

int age = 39;
const int * pt = &age;

*pt += 1;   // invalid
cin >> *pt; //  invalid

*pt = 20;   // invalid
age = 20;   // valid

pt 指向一个 const int,因此不能使用 pt 来修改这个值,*pt 的值为 const,不能修改。

以前,我们将常规变量的地址赋给常规指针,而这里将常规变量的地址赋给指向 const 的指针。因此有两种可能:

const float g_earth = 9.80;
const float * pe = &g_earth;    // valid

const float g_moon = 1.63;
float * pm = &g_moon;           // invalid

对于第一种情况来说,既不能使用 g_earth 来修改值 9.80,也不能使用 pe 来修改。对于第二种情况,C++ 禁止将 const 的地址赋给非 const 指针。

如果将指针指向指针,则情况将更加复杂,假如设计的是一级间接关系,则将非 const 指针赋给 const 指针是可以的。

int age = 39;           // age++ is a valid operation
int * pd = &age;        // *pd = 41 is a valid operation
const int * pt = pd;    // *pt = 42 is an invalid operation

进入两级间接关系时,与一级间接关系一样将 const 和非 const 混合的指针复制方式将不再安全。如果允许这样做,则可以编写这样的代码:

const int **pp2;
int *p1;
const int n = 13;
pp2 = &p1;      // not allowed, but suppose it were
*pp2 = &n;      // valid, both const, but sets p1 to point at n
*p1 = 10;       // valid but changes const n

上述代码将非 const 地址(&p1)赋给了 const 指针(pp2),因此可以使用 p1 来修改const 数据。

注意:如果数据类型本省并不是指针,则可以将 const 数据或非 const 数据的地址赋给指向 const 的指针,但只能将非 const 数据的地址赋给非 const 指针。

对于由 const 数据组成的数组,则禁止将常量数组的地址赋给非常量指针将意味着不能将数组名作为参数传递给使用非常量形参的函数。

4. 函数和二位数组

为编写将二位数组作为参数的函数,相应的形参是一个指针,因为数组名被视为其地址。假设有如下代码:

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

对于函数 sum() 的函数,data 是一个数组名,该数组有 3 个元素,每一个元素本身是一个数组,由 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);

上述两个原型都指出,ar2 是指针而不是数组。指针类型指出,它指向由 4 个 int 组成的数组。因此,指针类型指定了猎术,这就是没有将列数作为独立的函数参数进行传递的原因。由于指针类型指定了列数,因此 sum() 函数只能接受由 4 列组成的数组。但长度变量指定了行数,因此 sum() 对数组的行数没有限制。
行数被传递给 size 参数,但列数都是固定的——4 列。由于 ar2 指向数组的第一个元素,因此表达式 ar2 + r 指向编号为 r 的元素。

5. 函数和 C-风格字符串

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

  • char 数组
  • 用引号括起的字符串常量(也称字符串字面值)
  • 被设置为字符创的地址的 char 指针。

但上述 3 种选择的类型都是 char 指针(char*)。可以说是将字符串作为参数来传递,但实际传递的是字符串第一个字符的地址。这意味着字符串函数原型应将其表示字符串的形参声明为 char* 类型。

返回 C-风格字符串

可以在函数中创建 C-风格字符串并返回其地址,如:

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

变量 pstr 的作用域为 buildstr 内,因此在函数结束时,pstr(而不是字符串)使用的内存将被释放。但由于函数返回了 pstr 的值,因此程序仍可以通过 main() 中的指针来访问新建的字符串。

你可能感兴趣的:(C++)