面向对象程序设计——Visual C++
第3章 函数和编译预处理

思考题

P98

1、C++函数原型中的参数名与函数定义中的参数名以及函数调用中的参数名必须一致吗?为什么?

不必须一致。函数原型中的参数名只是为了方便程序员理解参数的含义,而函数定义中的参数名是为了在函数体内使用参数,函数调用中的参数名是为了传递实际参数值。
在函数原型中,参数名可以省略,只需要指定参数类型即可。在函数定义和函数调用中,参数名必须与函数原型中的参数类型一致,但可以不同于函数原型中的参数名。这是因为在函数定义和函数调用中,参数名只是一个标识符,用于在函数体内引用参数值,而参数类型才是真正的参数标识符。

2、在C++中,什么叫做嵌套定义?什么叫做嵌套调用?什么叫做递归调用?

嵌套定义是指在一个函数或类中定义另一个函数或类。例如,在一个类中定义一个成员函数,这个成员函数中又定义了另一个函数,这就是嵌套定义。
嵌套调用是指在一个函数中调用另一个函数,而被调用的函数也可能会调用其他函数,形成多层嵌套的调用关系。例如,在一个函数中调用另一个函数,而被调用的函数中又调用了另一个函数,这就是嵌套调用。
递归调用是指一个函数直接或间接地调用自身。递归调用通常用于解决需要重复执行相同操作的问题,例如计算阶乘、斐波那契数列等。递归调用需要注意控制递归深度,避免出现无限递归的情况。

3、在C++中,如何将一个函数声明为内联函数?它有哪些特点?

在C++中,可以使用关键字inline将一个函数声明为内联函数。例如:

inline int add(int a, int b) {
    return a + b;
}

内联函数的特点包括:

  1. 编译器会将内联函数的代码直接插入到调用该函数的地方,而不是像普通函数一样进行函数调用和返回操作,从而提高程序的执行效率。
  2. 内联函数通常适用于函数体较小、调用频繁的函数,例如一些简单的数学运算函数。
  3. 内联函数的定义必须放在头文件中,以便在多个源文件中使用。
  4. 内联函数的使用需要权衡代码大小和执行效率之间的平衡,过多的内联函数可能会导致代码膨胀,从而影响程序的可维护性和可读性。

4、在C++中,重载的函数是通过什么来区分的?

在C++中,重载的函数是通过函数的参数类型、参数个数、参数顺序以及const和引用等修饰符来区分的
当调用一个重载函数时,编译器会根据实际参数的类型、个数和修饰符等信息来匹配最合适的函数。如果找不到匹配的函数,则会发生编译错误。重载函数的返回类型和函数名不参与函数重载的区分。

5、作用域和存储类分别有什么含义?C++中有哪几种类型的作用域?有哪几种类型的存储方式?

作用域指的是变量、函数、类等标识符在程序中的可见范围
C++中有四种作用域:全局作用域、命名空间作用域、类作用域和局部作用域

存储类指的是变量或对象在程序中的存储方式和生命周期
C++中有五种存储类:auto、register、static、extern和mutable。 其中,auto存储类用于自动推断变量类型;register存储类用于将变量存储在CPU寄存器中,以提高访问速度;static存储类用于将变量存储在静态存储区中,使其在程序运行期间一直存在;extern存储类用于声明一个在其他文件中定义的全局变量或函数;mutable存储类用于修饰类的成员变量,使其可以在const成员函数中被修改。

总之,作用域和存储类是C++中非常重要的概念,对于程序的正确性和性能都有着重要的影响。程序员需要深入理解它们的含义和用法,才能写出高质量的C++代码。

6、C++中具有默认参数值的函数的作用是什么?在什么情形下使用默认的参数值。

C++中具有默认参数值的函数可以简化函数调用的过程,使得在调用函数时可以省略一些参数,从而提高代码的可读性和可维护性。默认参数值可以在函数声明或定义时指定,如果在函数调用时省略了某些参数,则会使用默认参数值来代替。
默认参数值通常适用于一些常用的参数,例如文件名、缓冲区大小等,这些参数的默认值可以在函数定义中指定,从而避免在每次调用函数时都需要指定这些参数。需要注意的是,如果函数有多个参数,那么只能将最后一个或最后几个参数设置为默认参数值,否则会导致编译错误。此外,如果函数的参数类型是指针或引用类型,那么默认参数值应该设置为nullptr或空引用,以避免出现不可预期的错误。

7、编译预处理指令的特征是什么?在C++中有几种形式的编译预处理指令?

编译预处理指令是在编译过程中对源代码进行处理的指令,其特征包括以#开头,不需要分号结尾,可以出现在任何位置,不受语法限制,不参与程序的编译和链接过程
在C++中,有四种形式的编译预处理指令,分别是**#define、#include、#ifdef和#ifndef**。其中,#define用于定义宏,可以用于简化代码、提高可读性和可维护性;#include用于包含头文件,可以将代码分离成多个文件,提高代码的模块化和复用性;#ifdef和#ifndef用于条件编译,可以根据条件判断是否编译某段代码,从而实现跨平台、调试等功能。需要注意的是,编译预处理指令的滥用会导致代码的可读性和可维护性降低,因此应该谨慎使用。

习题

1、设计一个函数,要求将其字符型的参数转化为小写字母。

# 使用标准库函数
#include <cctype> // 包含tolower()函数的头文件
#include <cstring> // 包含strlen()函数的头文件
void toLower(char* str) {
    int len = strlen(str);
    for (int i = 0; i < len; i++) {
        str[i] = tolower(str[i]);
    }
}
# 不使用标准库函数
#include <iostream>
using namespace std;
void toLower(char *str) {
    // 遍历字符串,将每个字符转为小写字母
    for (int i = 0; str[i] != '\0'; i++) {
        // 如果字符是大写字母,则转为小写字母
        if (str[i] >= 'A' && str[i] <= 'Z') {
            str[i] += 'a' - 'A';
        }
    }
}

2、编写一个函数,将华氏温度转换为摄氏温度。公式为C=(F-32)*5/9。

//将华氏温度转换为摄氏温度的函数
double FahrenheitToCelsius(double f) {
    double c = (f - 32) * 5 / 9; //根据公式计算摄氏温度
    return c; //返回摄氏温度
}

3、设计一个程序,输入两个整数,求出这两个整数的最大公约数和最小公倍数,要求用两个函数分别求最大公约数和最小公倍数。

#include <iostream>
using namespace std;
// 求最大公约数
int gcd(int a, int b) {
    int r = a % b;
    while (r != 0) {
        a = b;
        b = r;
        r = a % b;
    }
    return b;
}
// 求最小公倍数
int lcm(int a, int b) {
    return a * b / gcd(a, b);
}
int main() {
    int a, b;
    cout << "请输入两个整数: ";
    cin >> a >> b;
    cout << "它们的最大公约数是: " << gcd(a, b) << endl;
    cout << "它们的最小公倍数是: " << lcm(a, b) << endl;
    return 0;
}

5、编写一个函数trans(int n, int base)实现数制转换。第一个参数n为要转换的十进制数,第二个参数可为2、8、16、32,分别表示将n转换为二进制、八进制数、十六进制数和三十二进制数。第二个参数的默认值为8。

#include <iostream>
#include <cstring>
using namespace std;
const int MAX_LEN = 100; //定义最大的存储长度
char hexTable[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; //十六进制转换表
void reverse(char s[]) {
    int len = strlen(s);
    for (int i = 0; i < len / 2; ++i) {
        char temp = s[i];
        s[i] = s[len - i - 1];
        s[len - i - 1] = temp;
    }
}
void trans(int n, int base = 8) {
    char s[MAX_LEN];
    int i = 0;
    do {
        int t = n % base;
        if (base == 16) {
            s[i++] = hexTable[t]; //将十六进制转换为字符
        } else {
            s[i++] = t + '0'; //其他进制直接转换为对应的字符
        }
        n /= base;
    } while (n > 0);

    s[i] = '\0'; //添加字符串结束符

    reverse(s); //翻转字符串

    cout << s << endl; //输出转换后的字符串
}
int main() {
    int n, base;
    cout << "请输入一个十进制整数:";
    cin >> n;
    cout << "请输入目标进制(默认为8):";
    cin >> base;
    if (base != 2 && base != 8 && base != 16 && base != 32) { //判断输入是否合法
        cout << "输入的目标进制不合法!" << endl;
        return 1;
    }
    trans(n, base);
    return 0;
}

6、编写一个函数判断一个整数是否为素数。并在主函数中通过该函数求出所有三位数的素数,要求每行输出5个素数。

#include <iostream>
using namespace std;
// 判断一个整数是否为素数,是返回true,不是返回false
bool IsPrime(int num){
    if(num <= 1) return false;  // 1以下的数都不是素数
    for(int i=2; i*i<=num; i++){
        if(num%i == 0) return false;  // 如果可以整除,则不是素数
    }
    return true;
}
int main(){
    int count = 0;  // 记录输出素数的个数
    for(int i=100; i<=999; i++){
        if(IsPrime(i)){  // 判断i是否为素数
            cout << i << " ";
            count++;
            if(count % 5 == 0) cout << endl;  // 每输出5个素数换行
        }
    }
    return 0;
}

结果:

101 103 107 109 113
127 131 137 139 149
151 157 163 167 173
179 181 191 193 197
199 211 223 227 229
233 239 241 251 257
263 269 271 277 281
283 293 307 311 313
317 331 337 347 349
353 359 367 373 379
383 389 397 401 409
419 421 431 433 439
443 449 457 461 463
467 479 487 491 499
503 509 521 523 541
547 557 563 569 571
577 587 593 599 601
607 613 617 619 631
641 643 647 653 659
661 673 677 683 691
701 709 719 727 733
739 743 751 757 761
769 773 787 797 809
811 821 823 827 829
839 853 857 859 863
877 881 883 887 907
911 919 929 937 941
947 953 967 971 977
983 991 997

7、编写三个重载函数,分别求两个整数、实数和双精度型数中最大的数。

#include<iostream>
using namespace std;
int max(int a, int b);
double max(double a, double b);
long double max(long double a, long double b);
int main() {
    cout << "Max between 10 and 20: " << max(10, 20) << endl;
    cout << "Max between 1.2 and 2.2: " << max(1.2, 2.2) << endl;
    cout << "Max between 10.5 and 20.5: " << max(10.5L, 20.5L) << endl;
    return 0;
}
// 求两个整数中最大的数
int max(int a, int b) {
    return (a > b) ? a : b; // 三目运算符判断a和b中的最大值并返回
}
// 求两个实数中最大的数
double max(double a, double b) {
    return (a > b) ? a : b; // 三目运算符判断a和b中的最大值并返回
}
// 求两个双精度型数中最大的数
long double max(long double a, long double b) {
    return (a > b) ? a : b; // 三目运算符判断a和b中的最大值并返回
}

8、定义一个内联函数,求任一个实数的绝对值。

inline double abs(double x)
{
    return (x >= 0) ? x : -x;
}

9、编写一个递归函数Power(int x, int y),计算x的y次幂,并在主函数中实现输入输出。

#include <iostream>
using namespace std;
int Power(int x, int y) {
    if (y == 0) { // 任何数的0次幂都等于1
        return 1;
    } else if (y == 1) { // 任何数的1次幂都等于本身
        return x;
    } else if (y < 0) { // 如果指数y是负数,则将x的y次幂转化为x的1/y次幂再取倒数
        return 1 / Power(x, -y);
    } else if (y % 2 == 0) { // 如果指数y是偶数,则将x的y次幂转化为x的y/2次幂的平方
        int temp = Power(x, y/2);
        return temp * temp;
    } else { // 如果指数y是奇数,则将x的y次幂转化为x的(y-1)次幂乘x
        return x * Power(x, y-1);
    }
}
int main() {
    int x, y;
    cout << "请输入底数x和指数y(以空格分隔):" << endl;
    cin >> x >> y;
    cout << x << "的" << y << "次幂为:" << Power(x, y) << endl;
    return 0;
}

11、用递归的方法求出勒让德多项式的第n项的值。并在主函数中根据输入的x值求勒让德多项式的前20项,要求每行输出5项。

#include <iostream>
#include <iomanip>
using namespace std;
double Legendre(int n, double x) {
    if (n == 0) return 1.0;
    if (n == 1) return x;
    return ((2*n-1)*x*Legendre(n-1, x) - (n-1)*Legendre(n-2, x))/n;
}
int main() {
    double x;
    cout << "Please enter the value of x: ";
    cin >> x;
    cout << "The first 20 Legendre polynomials of x are: " << endl;
    for (int i = 0; i < 20; i++) {
        cout << setw(10) << setprecision(6) << Legendre(i, x) << " ";
        if ((i+1)%5 == 0) cout << endl;
    }
    return 0;
}

12、编程求解问题:若一头小母牛,从出生起第四个年头开始每年生一头母牛,按此规律,第n年时共有多少头母牛?试分别用递归法和递推法实现。

#include <iostream>
using namespace std;

int count_cows(int n) {
    if (n <= 0) { // 无效输入
        return 0;
    } else if (n <= 3) { // 前三年,母牛数量不变
        return n;
    } else { // 第n年的母牛数量等于第n-1年的母牛数量加上第n-3年的母牛数量
        return count_cows(n - 1) + count_cows(n - 3);
    }
}

int main() {
    int n = 20;
    for (int i = 1; i <= n; i++) {
        cout << count_cows(i) << " ";
        if (i % 5 == 0) {
            cout << endl;
        }
    }
    return 0;
}
#include <iostream>
using namespace std;
int count_cows(int n) {
    int f1 = 1, f2 = 2, f3 = 3, f;
    if (n <= 0) { // 无效输入
        return 0;
    } else if (n <= 3) { // 前三年,母牛数量不变
        return n;
    } else { // 递推计算
        for (int i = 4; i <= n; i++) {
            f = f1 + f3;
            f1 = f2;
            f2 = f3;
            f3 = f;
        }
        return f;
    }
}
int main() {
    int n = 20;
    for (int i = 1; i <= n; i++) {
        cout << count_cows(i) << " ";
        if (i % 5 == 0) {
            cout << endl;
        }
    }
    return 0;
}

14、设计一个函数,求n的阶乘,其中参数n的默认值为5。

int factorial(int n = 5){
    int result = 1;
    for(int i = 1; i <= n; i++){
        result *= i;
    }
    return result;
}

15、设计一个参数数目可变的函数,该函数求出n个数中的最大数并输出,其中n为该函数的第一个参数,表示可变参数的个数。

#include <cstdarg>  // 头文件,定义了可变参数相关的函数和宏

using namespace std;

// 可变参数模板函数,求出n个数中的最大值
template<typename T>
T max_of_n(int n, T arg1, ...)
{
    T max_val = arg1;
    va_list args;    // 可变参数列表
    va_start(args, arg1);   // 初始化可变参数列表
    for (int i = 1; i < n; ++i) {
        T val = va_arg(args, T);  // 从可变参数列表中获取参数
        if (val > max_val) {
            max_val = val;
        }
    }
    va_end(args);  // 结束可变参数列表
    return max_val;
}

int main()
{
    // 测试用例
    int max1 = max_of_n<int>(5, 1, 2, 3, 4, 5);  // 5
    double max2 = max_of_n<double>(4, 1.2, 3.4, 2.3, 4.5);  // 4.5
    cout << "max1 = " << max1 << endl;
    cout << "max2 = " << max2 << endl;
    return 0;
}

16、设计一个程序,定义带参数的宏MAX(A,B)和MIN(A,B),分别求出两数中的大数和小数。在主函数中输入三个数,并求出这三个数中的最大数和最小数。

#include <stdio.h>
#define MAX(A,B) ((A)>(B)?(A):(B))
#define MIN(A,B) ((A)<(B)?(A):(B))
int main() {
    int a, b, c;
    printf("请输入三个整数:");
    scanf("%d%d%d", &a, &b, &c);
    int max_num = MAX(MAX(a, b), c); // 求最大值
    int min_num = MIN(MIN(a, b), c); // 求最小值
    printf("最大值为:%d\n", max_num);
    printf("最小值为:%d\n", min_num);
    return 0;
}

17、用条件编译方法实现以下功能:

输入一行电报文字,可有两种输出方式,一种方式为原文输出,另一种方式将字母变成其下一个字母输出(如‘a’变成’b’,……,‘z’变成’a’。其他字符不变)。用#define命令来控制是否要翻译成密码,例如: #define CHANGE 1 则输出密码,若 #define CHANGE 0 则不译成密码,按原码输出。

#include <iostream>
#include <cstring>

#define CHANGE 1 // 定义CHANGE为1时,将输入文本翻译成密码,为0时输出原文本
using namespace std;
int main() {
    char text[100]; // 输入文本
    cout << "请输入一行电报文字:" << endl;
    cin.getline(text, 100); // 读取输入文本

#ifdef CHANGE
    for (int i = 0; i < strlen(text); i++) {
        if (text[i] >= 'a' && text[i] < 'z') {
            cout << char(text[i] + 1); // 将小写字母变成其下一个字母输出
        } else if (text[i] == 'z') {
            cout << 'a'; // 将字母z变成a输出
        } else {
            cout << text[i]; // 其他字符不变输出
        }
    }
#else
    cout << text; // 输出原文本
#endif

    return 0;
}

实验题

2、分析以下程序的运行结果:

#include <iostream.h>
int sum(int);
void main(){
	int s;
	for(int i=1;i<=10;i++)
		s=sum(i);
	cout<<”s=”<<s<<endl;
}
int sum(int k){
	static int x=0;	//A
	return (x+=k);
}

结果:s=55

3、将上题中的A行换成:

int x=0; 则程序的输出结果有什么不同?为什么?
答:如果将上题中的A行替换成int x=0;,程序的输出结果将是10。这是因为在这种情况下,x是一个普通的局部变量,每次调用sum函数时都会重新初始化为0,所以函数每次都只会返回传入的参数k的值。在主函数中,调用sum函数10次,每次传入的参数i110递增,因此程序最终输出的结果是10
与前面的情况相比,这里的x不再是静态变量,所以它的生命周期只存在于函数调用的过程中,每次函数调用结束后都会被销毁。因此,每次调用sum函数时,变量x的初始值都是0,而不是上一次函数调用结束时的值。这也就导致了程序输出结果的不同。

4、分析下列程序的运行结果:

#include <iostream.h>
int Func(int,int);
void main(){
	extern int x,y;
	cout<<Func(x,y)<<endl;
}
int x=15, y=-5;
int Func(int a, int b){
	int s;
	s=a+b+x+y;
	return s;
}

结果:20

5、分析下列程序的运行结果:

#include <iostream.h>
int add(int x=15,int y=10){
	return x+y;
}
void main(){
	int a=8;
	cout<<add()<<’\t’;
	cout<<add(a)<<’\t’;
	cout<<add(a,add(a))<<’\t’;
	cout<<add(a,add(a,add()))<<’\n’;
}

结果:25 18 26 41