『踩坑记录』Test.main()函数执行后的输出是?

本文深入解析了一段Java代码的执行过程,涉及类继承、方法覆盖及finally块的执行顺序。通过具体实例,详细解释了B类继承A类,并在构造函数中调用父类构造函数与自身方法的过程,以及方法覆盖对执行结果的影响。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本篇博客我们来看一道在牛客上遇到的问题。

我们来看题目
Test.main() 函数执行后的输出是()

public class Test {  
    public static void main(String [] args) {  
        System.out.println(new B().getValue());  
    }
    
    static class A {  
        protected int value;
        
        public A(int v) {
            setValue(v);  
        }
        
        public void setValue(int value) {  
            this.value = value;  
        }
        
        public int getValue() {  
            try {  
                value++;  
                return value;  
            } catch(Exception e) {  
                System.out.println(e.toString());  
            } finally {  
                this.setValue(value);  
                System.out.println(value);  
            }
            return value;  
        }
    }
    
    static class B extends A {  
        public B() {  
            super(5);  
            setValue(getValue() - 3);  
        }  
        
        public void setValue(int value) {  
            super.setValue(2 * value);  
        }  
    }  
}

我们来分析一下这段代码的执行结果

  • 首先new B()会去执行B的构造方法。
    B的构造方法中,第一行为super(5);,此时回去执行A的构造方法,A的构造方法中调用setValue(int value);,注意此时由于B重写了该方法,所以执行的是B中的setValue(int value);,所以会去执行super.setValue(2 * value);,进而去执行A中的setValue(int value);方法。传入的参数为2 * 5 = 10,此时value的值为10
    下面执行B的构造方法中的第二行setValue(getValue() - 3);,由于B中没有getValue()方法,所以会去调用A类中的getValue()方法。try代码块中返回的10 + 1 = 11,暂时保存在栈中,此时调用finally块中的this.setValue(value);,同样的会去调用B中的setValue(int value)方法,执行super.setValue(2 * value),传入的为22,此时value被设置为22,执行System.out.println(value);打印的结果就是22。此时,保存在栈中的11被返回,继续执行setValue(getValue() - 3);,此时调用setValue(int value)传入的参数为11 - 3 = 8,然后在B中的setValue(int value)方法中执行super.setValue(2 * value);,调用A类的setValue(int value)方法,传入的参数为2 * 8 = 16。此时value被设置为16。
    到这里,new B()执行完毕。
  • 然后执行new B().getValue()
    B中没有getValue()方法,所以会去执行A类中的getValue()方法,try块中对value进行自增,value变为17,保存到栈中。然后去执行finally块中的代码,执行this.setValue(value);会去执行B类中的setValue(int value);方法,进而执行super.setValue(2 * value);语句,传入参数为34,此时value被设置为34,然后执行System.out.println(value);打印的结果就是34
  • 最后主函数打印返回值,即保存在栈中的数据17

所以该程序运行的结果为
22
34
17

我们执行一下该代码看一下结果
在这里插入图片描述

<think>嗯,用户现在聚焦于如何在C++中安全使用预处理器宏以避免编译错误,这确实是个实用且容易的话题。从对话历史看,用户已经了解到预处理器的基础功能和常见错误类型,现在需要更深入的最佳实践指导。用户可能正在开发跨平台项目,或是遇到宏相关的编译错误急需解决方案。考虑到ta引用的资料特别强调宏的局限性[^1]和类型安全问题[^4],深层需求其实是:如何在保留宏优点的同时规避其风险。这需要从工程实践角度给出具体方案。从技术层面,需要覆盖几个关键痛点:宏参数的安全处理(特别是运算符优先级问题)、条件编译的健壮性写法、如何替代危险宏场景。用户提供的引用[^4]中那个SQUARE宏案例特别经典——必须重点讲解括号防御式编写技巧。另外注意到用户对跨平台兼容性的关注[^5],这点要融入每个实践建议中。比如Windows下能正常工作的连接符##在Linux可能失败,这种细节必须强调。准备这样结构化回答:1.宏参数安全处理(括号规则)2.多表达式宏的do-while防御模式3.条件编译规范(避免嵌套过深)4.类型安全替代方案(inline/template)5.符号操作技巧(#/##的跨平台限制)每个点都要给出反例和修正代码,特别是引用[^4]提到的运算符优先级问题。对于高级用户可能关心的元编程场景,可以提一下编译期计算替代方案。</think> 以下是C++中安全使用预处理器宏避免编译错误的最佳实践,结合技术原理和代码示例说明: ### 一、宏参数安全处理原则 1. **始终用括号包裹参数和整个宏** ```cpp // 错误示例:易产生运算符优先级问题 #define SQUARE(x) x * x // SQUARE(1+1) → 1+1*1+1 = 3 // 正确做法:双重括号防护 #define SAFE_SQUARE(x) ((x) * (x)) // ((1+1)*(1+1)) = 4 ``` *若参数含逗号(如`std::pair`),需额外用括号包裹参数*: ```cpp #define LOG_PAIR(p) std::cout << (p).first << (p).second ``` 2. **多表达式宏使用`do { ... } while(0)`封装** ```cpp // 避免if-else作用域错误 #define SAFE_MACRO(a, b) do { \ std::cout << (a); \ (b) += 10; \ } while(0) // 使用场景 if (cond) SAFE_MACRO(x, y); // 展开后:do {...} while(0); 带分号 else // 正常执行 ``` ⚠️ 此结构确保宏在任何控制流中行为一致[^4] ### 二、符号操作的安全规范 1. **`#`字符串化运算符的约束** ```cpp #define STRINGIFY(x) #x std::string s1 = STRINGIFY(123); // 合法:"123" // std::string s2 = STRINGIFY(a,b); // 错误:含逗号需额外处理 ``` 2. **`##`符号连接的限制场景** ```cpp // 仅限连接合法标识符(变量名/函数名) #define MAKE_FUNC(name) void name##_func() MAKE_FUNC(test); // 生成 void test_func(); // 禁止连接数字或非标识符 // #define CONCAT(a,b) a##b // CONCAT(1,2) → 12 (非法) ``` ### 三、条件编译的健壮写法 ```cpp #ifndef CONFIG_H // 头文件保护 #define CONFIG_H #if defined(_WIN32) // 明确检查宏是否存在 #define API __declspec(dllexport) #elif defined(__linux__) #define API __attribute__((visibility("default"))) #endif #endif // CONFIG_H // 配对注释避免嵌套错误 ``` *关键点*: - 用`#if defined()`替代`#ifdef`(支持复合条件) - 平台宏名称标准化(如`_WIN32`非`WIN32`) - 每个`#if`/`#ifdef`必有配对的`#endif`[^4] ### 四、安全替代方案(优先选择) 1. **用`constexpr`替代常量宏** ```cpp // 取代 #define PI 3.14 constexpr double PI = 3.14; // 类型安全且可调试 ``` 2. **用`inline`函数替代函数宏** ```cpp // 取代 #define MAX(a,b) ((a)>(b)?(a):(b)) template<typename T> inline T max(T a, T b) { return a > b ? a : b; } // 无副作用风险 ``` 3. **用`constexpr`函数实现编译期计算** ```cpp constexpr int factorial(int n) { return (n <= 1) ? 1 : n * factorial(n-1); } // 取代计算宏 ``` ### 五、调试与验证技巧 1. **查看宏展开结果** ```bash g++ -E main.cpp -o main.ii # 输出预处理文件 ``` 2. **编译器警告选项** ```bash g++ -Wall -Wextra -Wpedantic # 开启所有宏相关警告 ``` > **核心原则**:宏应是最后手段,优先使用类型安全的现代C++特性[^1]。若必须使用宏,需严格遵循括号规则、作用域封装和平台兼容性检查[^4]。 ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值