CPP-07-函数

函数的定义如下

1
2
3
4
5
6
7
8
9
10
11
void functionName(parameterList) 
{
statement(s);
return; //optional
}

typeName functionName(parameterList)
{
statement(s);
return value; //value is type cast to type typeName
}

为什么有时候会需要原型?

编译器在搜索文件的剩余部分时将必须停止对main( )的编译。函数可能并不在文件中,C++允许将一个程序放 在多个文件中,单独编译这些文件,然后再将它们组合起来。在这种情况下,编译器在编译main( )时,可能无权访问函数代码。如果函数位于库中,情况也将如此。避免使用函数原型的唯一方法是,在首次使用函数之前定义它,但这并不总是可行的

1
2
double cube(double);	//OK
void say_bye(...);

函数原型不要求提供变量名,有类型列表就足够了

不指定参数列表时应使用省略号

参数数组

C通常 按值传递 参数,这意味着将 数值参数传递给函数,而后者将其赋给一个新的变量。出于简化的目的,C标准使用参数(argument)来表示实参,使用参量(parameter)来表示形参

但是在传递数组的时候,传递的是指针(数组的地址,也是值传递),将数组名解释为其第一个元素的地址

1
int sum_arr(int arr[], int n)	//arr = array, n = size

所以可以直接使用指针

1
int sum_arr(int *arr, int n)

但也不能完全相等,因为 int * arr 还可以表示为一个整形的指针

因为数组作为函数参数传递的是指针,意味着函数内部可能修改数组内容,为了防止组数在函数内被修改可以使用const 保护数组

1
void show_array(const double arr[], int num);

const 意味着arr不能被修改,否则编译器将报错

指针与const

const 用法有两种

  1. 让指针指向一个常量对象,可以防止使用该指针来修改所指向的值
  2. 将指针本身声明为常量,这样可以防止改变指针指向的位置

首先,声明一个指向常量的指针

1
2
3
4
5
6
int age = 39;
const int *pt = &age;
const int num = 40;
//*pt += 1;

cout << *pt << " " << age << " " << num << endl;

pt,是一个指针(*pt),指针的类型是 const int,也就是 *pt 指向了一个常量,不能使用 *pt 来修改它

1
2
3
*pt = 1;	//INVALID because pt points to a const int
age = 2;
num = 4; //cannot assign to variable 'num' with const-qualified type 'const int'

这个声明意味着 *pt 指向了一个常量,不能通过 pt 修改,但并不意味着 age 是一个常量

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

1
2
3
4
const int months[12] = {31, 28, 31, 30, 31};
int sum(int arr[], int n);
int j = sum(months, 12); //not allowed
months[0] = 1; //cannot assign to variable 'months' with const-qualified type 'const int[12]'

尽可能使用 const

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

  • 这样可以避免由于无意间修改数据而导致的编程错误;
  • 使用const使得函数能够处理const和非const实参,否则将只能接受非const数据。 如果条件允许,则应将指针形参声明为指向const的指针
1
2
3
4
5
int age = 39;
int num = 40;
const int *pt = &age;
pt = &num;
cout << *pt << endl; //40

声明中的const只能防止修改pt指向的值(这里为39),而不能防止修改pt的值

1
2
3
4
5
int age = 39;
int num = 40;
int * const pt = &age;
pt = &num; //cannot assign to variable 'pt' with const-qualified type 'int *const'
cout << *pt << endl;
cpp_const_0729_1611

其他参数

将二维数组作为参数的函数,必须牢记,数组名被视为其地址,因此,相应的形参是一个指针,就像一维数组一样。比较难处理的 是如何正确地声明指针

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

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

data[r][c] == *(*(data + r) + c) //same thing

其中 arr[] 表示的就是一个数组的地址,然后 int arr[][4]表示二维数组,sum 只能接收 4 列的二维数组,但是行数却没有确定

将字符串作为数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
unsigned int c_in_str(const char * str, char ch);

int main() {
char m[15] = "hello world";
//char * str = "hello world"; //error
const char * n = "hello world";
cout << c_in_str(m, 'o') << " " << c_in_str(n, 'e') << endl; //2 1
return 0;
}

unsigned int c_in_str(const char * str, char ch)
{
unsigned int count = 0;
while(*str) //quit wheen *str is '\0'
{
if(*str == ch) count++;
str++;
}
return count;
}

注意 **字符串需要在 c++11 中赋值给 const char **,否则 ISO C++11 does not allow conversion from string literal to 'char *'

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
char * buildstr(char c, int n);

int main() {
char * result = buildstr('h', 5);
cout << result << endl; //hhhhh
delete [] result;
return 0;
}

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

注意事项

  1. 删除的时候别忘了 delete [] result
  2. 构建指定长度的字符数组,长度为 n + 1,且最后设置 \0

函数指针结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
struct polar
{
int x;
int y;
};

void show_polar(const polar * po)
{
cout << "position x: " << po->x << " y: " << po->y << endl;
}

int main() {
polar * p = new polar;
p->x = 100;
p->y = 200;
show_polar(p);

return 0;
}

输出结果:position x: 100 y: 200

函数指针

函数的地址是存储其机器语言代码的内存的开始地址,可以编写将另一个函数的地 址作为参数的函数。这样第一个函数将能够找到第二个函数,并运行它

使用函数指针必须

  1. 获取函数的地址,使用函数名(后面不跟参数)即可
  2. 声明一个函数指针,声明应像函数原型那样指出有关函数的信息
  3. 使用函数指针来调用函数
1
2
double pam(int);	//prototype
double (*pf)(int); //pf pointts to a function that take one int argumeent and that returns type double

定义函数原型,然后使用 (*func_point) 替换 func_name 即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int sum(int x, int y)
{
return x + y;
}

int del(int x, int y)
{
return x - y;
}

int mix(int x,int y, int (*ptr)(int y, int z))
{
return ptr(x, y);
}

int (*ptr)(int x, int y);
ptr = sum;
cout << ptr(1, 2) << endl;
ptr = del;
cout << ptr(1, 2) << endl;
cout << mix(1, 2, sum) << endl;
cout << mix(1, 2, del) << endl;
cout << mix(1, 2, ptr) << endl;

运行结果

1
2
3
4
5
3
-1
3
-1
-1

表示函数指针数组

1
const double * (*pa[3])(const double *, int) = {f1, f2, f3};

自动类型推断只能用于单值初始化, 而不能用于初始化列表,所以

1
2
3
4
5
6
7
const double *px = pa[0](av, 3);
auto pb = pa;
auto pc = &pa;
const double *py = (*pb[1])(av, 3);
//*pd[3] //an array of 3 ponter
//(*pd)[3] // a pointer to an array of 3 elements
const double *(*(*pd)[3])(const double*, int) = &pa;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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;
}

double av[3] = {1112.3, 1542.6, 2227.9};
const double * (*p1)(const double *, int) = f1;
auto p2 = f2;
cout << (*p1)(av, 3) << ": " << *(*p1)(av, 3) << endl;
cout << p2(av, 3) <<": " << *p2(av, 3) << endl;

输出结果

1
2
0x7ff7b6fc8440: 1112.3
0x7ff7b6fc8448: 1542.6

太过于复杂,可以使用 typedef 进行简化

1
2
3
4
5
typedef const double *(*p_fun)(const double ar[], int n);

double av[3] = {1112.3, 1542.6, 2227.9};
p_fun p1 = f1;
cout << *p1(av, 3) << endl;

参考链接

  1. 《C++ Primer Plus》