这里总结下C++函数的一些基础知识。在工作中深刻体会到基础的重要性,以及理论指导实践,必须有一定的理论知识作为依据,才能写出健壮的代码以及有不错的解决问题的能力。如果总结的知识不对或者已过时还请大家指正。
基本知识
C++对于返回值的类型有一定的限制:不能是数组,但是可以是其他类型:整数,浮点数,指针,结构和对象。
通常,函数通过将返回值复制到指定的CPU寄存器或内存单元中来将其返回。随后,调用程序将查看该内存单元。
在编译阶段进行的原型化被称为静态类型检查(static type checking)
函数参数和按值传递
按值传递时,当函数被调用时,该函数会对传递的实参进行拷贝,因此函数中的操作不会影响到调用者的数据。这种情况比价简单,不做代码示例。
函数和数组
基本使用
C++将数组名解释为第一个元素的地址,即有一下等式: arr = &arr[0]
并且 int sum_arr(int arr[], int n);
和 int sum_arr2(int * arr, int n);
这两个函数原型是等价的。在C++中, 当且仅当用于函数头或函数原型时, int arr[]
和 int * arr
的含义才是相同的。我们可以记住一下两个恒等式:
arr[i] = *(arr + i)
&arr[i] = arr + i
将数组作为参数传递说明了什么:说明调用函数时并没有把数组内容传递给函数,而是将数组的位置(地址),包含元素的种类以及元素数目提交给函数。这也是按值传递,只不过传的是数组的地址。
#include <iostream>
const int ArrSize = 8;
// prototype
int sum_arr(int arr[], int n);
int sum_arr2(int * arr, int n);
int main() {
using namespace std;
int cookies[ArrSize] = {1, 2, 4, 8, 16, 32, 64, 128};
std::cout << cookies << " = array address, ";
std::cout << sizeof cookies << " = sizeof cookies\n";
int sum = sum_arr(cookies, ArrSize);
std::cout << "Total cookies eaten: " << sum << std::endl;
sum = sum_arr(cookies, 3); // a lie
std::cout << "First three eaters ate " << sum << " cookies.\n";
sum = sum_arr(cookies + 4, 4); // another lie
std::cout << "Last four eaters ate " << sum << " cookies.\n";
return 0;
}
int sum_arr(int arr[], int n) {
std::cout << arr << " = arr, ";
std::cout << sizeof arr << " = sizeof arr\n";
int total = 0;
for (int i = 0; i < n; i++) {
total += arr[i];
}
return total;
}
// This is ok
int sum_arr2(int * arr, int n) {
std::cout << arr << " = arr, ";
std::cout << sizeof arr << " = sizeof arr\n";
int total = 0;
for (int i = 0; i < n; i++) {
total += *(arr + i);
}
return total;
}
输出结果:
0x7ffee07297f0 = array address, 32 = sizeof cookies
0x7ffee07297f0 = arr, 8 = sizeof arr
Total cookies eaten: 255
0x7ffee07297f0 = arr, 8 = sizeof arr
First three eaters ate 7 cookies.
0x7ffee0729800 = arr, 8 = sizeof arr
Last four eaters ate 240 cookies.
上述程序的结果显示,sizeof cookies = 32,而 sizeof arr = 4,因为 sizeof arr 只是指针的长度,从中可以得知指针本身没有数组的长度。
显示数组及用 const 保护数组
当编写显示数组的函数时,我们要注意确保原始数组不被修改,这是可以使用 const 修饰入参。这里要注意,当在函数原型中使用 const 修饰指针时,只能修饰指向基本类型的指针,而不能修饰指向指针的指针。
void show_array(const double ar[], int n);
以上函数原型表示,**ar指向的是常量数据,不能对其进行修改。**由此我们可以总结出常用的数组函数原型。
void f_modify(double ar[], int n);
void f_no_change(const double ar[], int n);
使用数组区间的函数
我们也可以用函数原型 int sum_arr(const int * begin, const int * end);
对数组的一个区间进行操作,以下是代码示例。
#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);
cout << "Total cookies eaten: " << sum << endl;
sum = sum_arr(cookies, cookies + 3); // first 3 elements
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";
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 += *pt;
}
return total;
}
函数和二维数组
假设有一下代码:
int data[3][4] = {{1,2,3,4}, {1,2,3,4}, {1,2,3,4}};
int total = sum(data, 3);
那么我们必须使用如下的原型:
int sum(int (*arr)[4], int n);
// or use this
int sum(int arr[][4], int n);
其中要注意的是 int (*arr)[4]
表示的是数组的指针,是含有4个int组成的指针,而 int * arr[4]
表示的是含有4个指针的数组,是数组。
函数和C风格字符串
表示字符串的方式有三种:
- char 数组
- 用引号扩起的字符串常量
- 被设置为字符串的地址的 char 指针
上述三种类型都是 char 指针,可以将其作为函数参数。
char ghost[15] = "galling";
char * str = "galling";
int n1 = strlen(ghost); // ghost is &ghost[0]
int n2 = strlen(str); // pointer to char
int n3 = strlen("galling"); // address of string
char数组和C风格字符串的一个重要区别是,字符串有内置的结束字符,而包含字符但不以空值字符结尾的 char 数组只是数组,不是字符串。
以下列举一些字符串处理函数原型
// 与普通数组不同,不用传字符串长度,可以通过空值判断是否到达结尾
unsigned int c_in_str(const char * str, char ch);
char * buildstr(char c, int n);
函数和结构
使用结构编写结构相关的函数时,一共有三种方法。
- 最直接的方式是和处理基本类型一样,按值传递,但这种方法需要复制结构,增加内存需求,降低系统的运行速度。
- 传递结构的地址
- 传递结构的引用
按值传递
按值传递代码案例
#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));
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;
return total;
}
void show_time(travel_time t) {
using namespace std;
cout << t.hours << " hours, "
<< t.mins << " minutes.\n";
}
output
Two-day total: 10 hours, 40 minutes.
Three-day total: 15 hours, 12 minutes.
这种情况就和处理基本类型的函数一样。
使用指针
传递结构的地址代码示例
#include <iostream>
#include <cmath>
// struct template
struct polar {
double distance;
double angle;
};
struct rect {
double x;
double y;
};
// prototypes
void rect_to_polar(const rect *pxy, polar *pda);
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); // pass addresses
show_polar(&pplace); // pass address
cout << "Next two numbers (q to quit): ";
}
cout << "DONE.\n";
return 0;
}
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 << " degrees\n";
}
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);
}
output
Enter the x and y values: 1
2
distance = 2.23607, angle = 63.4349 degrees
在函数中使用结构的指针避免了复制结构,整体效率更高。
按引用传递
入参是结构的函数最佳的还是按引用传递,因为这样省去了拷贝的过程
#include <iostream>
#include <cmath>
// struct template
struct polar {
double distance;
double angle;
};
struct rect {
double x;
double y;
};
// prototypes
void rect_to_polar(const rect &pxy, polar &pda);
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); // pass reference
show_polar(pplace); // pass reference
cout << "Next two numbers (q to quit): ";
}
cout << "DONE.\n";
return 0;
}
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 << " degrees\n";
}
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);
}
output
Enter the x and y values: 1
3
distance = 3.16228, angle = 71.5651 degrees
Reference
- 《C++ Primer Plus 6th》