力扣 二叉树的最大深度-104
二叉树的最大深度-104
class Solution {
public:
int maxDepth(TreeNode* root) {
return f(root,0);//调用函数f用先序遍历查找最长路径
}
int f(TreeNode* root,int sum)
{
if(root == nullptr)return sum;
int left = f(root->left,sum+1);
/*遍历左子树,以当前节点的左子节点为根,当前深度sum+1作为新的深度参数调用f函数,
这个过程会不断深入左子树,直到遇到左子树的叶子节点(root == nullptr)
,然后返回叶子节点向上回溯计算得到的左子树深度*/
int right = f(root->right,sum+1);
//完成左子树的深度计算时,开始递归遍历右子树,执行过程与遍历左子树相似
return left > right ? left : right;
//用于比较左子树的深度和右子树的深度,并返回较大的值,最后回到maxDepth函数,作为整棵树的最大深度。
}
};
每日问题
解释一下宏定义和函数调用的区别
宏定义和函数调用的区别
一、定义方式:
1.宏定义是一种预处理指令,在编译之前,预处理器会对代码中的宏进行文本替换。例如,定义一个简单的宏来计算平方:
#define SQUARE(x) ((x)*(x))
这里SQUARE(x)是宏定义,当预处理器在代码中发现SQUARE(5)这样的表达式时,它会将其替换为 ((5)*(5))
2.函数是一段具有特定功能的独立代码块,有明确的参数列表和返回值类型(也可以是void无返回值)。例如:
int square(int x){
return x*x;
}
这里定义了一个名为square的函数,它接受一个int类型的参数x,并返回x的平方
二、执行时机
1.宏展开是在编译预处理阶段进行的,在真正编译之前完成了文本替换。这意味着编译器看到的是替换后的代码。
2.函数调用是在程序运行时执行的。当程序执行到函数调用语句(如 int result = sqrare(5);)时,会跳转到函数的代码块执行,执行完后返回调用点继续执行后续代码
三、参数处理方式
1.宏定义中的参数没有类型检查。例如,在SQUARE宏中,如果写成SQUARE("abc"),预处理器依然会按照定义的规则进行替换,虽然这在实际的数学运算中没有意义,可能会导致编译错误,但预处理器不会像函数那样检查参数类型是否合适。
2.函数对参数有严格的类型检查。在上面的例子中,如果调用square函数时传入一个非int类型的参数(如double或char*等),编译器会发出类型不匹配的错误提示。
四、性能特点
1.对于简单的宏,因为它只是简单的文本替换,没有函数调用的开销(如参数传递、返回值处理等)。例如,如果在一个循环中频繁调用SQUARE宏来计算一个整数的平方,不会有函数调用的额外时间成本,但是,如果宏定义的内容很复杂,可能会导致生成的代码体积较大,因为每个使用宏的地方都会进行文本替换。
2.函数调用会有一定的开销,包括参数传递(可能涉及到参数的复制、栈帧的创建等)、返回值处理等。但是,函数的代码只存在一份,无论调用多少次,不会像宏那样因为多次使用而导致代码体积过度膨胀。
五、作用范围
1.宏定义的作用域通常从定义处开始,到文件末尾结束,除非被#undef指令取消定义。它不受函数作用域等的限制,本质上是一种文本替换规则,在预编译阶段全局生效。
2.函数有自己的作用域,函数内部定义的变量通常只在函数内部可见。函数可以通过返回值将结果返回给调用者,其作用范围和生命周期受到函数定义和调用规则的限制。
C/C++ 中的预处理器指令有哪些?举例说明其用途。
1.#include指令
用途:用于将指定的头文件内容包含到当前源文件中。头文件中通常包含函数声明、宏定义、类型定义等信息,这样可以在多个源文件中共享这些定义。
示例:
//在C++中包含输入/输出流头文件
#include<iostream>
//包含自定义头文件
#include"myheader.h"
对于这样的标准库头文件,编译器会在标准库路径中查找文件。而对于"myheader.h"这种自定义头文件,编译器通常会先在当前源文件所在目录查找,然后根据编译器设置的其他路径查找。通过包含iostream头文件,就可以使用cout和cin等表中输入输出流相关的功能。
2.#define指令
用途:用于定义宏。宏可以是一个简单的常量替换,也可以是带有参数的代码片段替换,它是一种文本替换机制,可以在编译前对代码进行简单的预处理。
示例-常量定义:
//定义一个常量PI
#define PI 3.14159
这样在代码中凡是出现PI的地方,预处理都会将其替换为3.14159。
示例-带参数的宏定义:
//定义一个宏来计算两个数的最大值
#define MAX(a,b)((a)>(b)?(a):(b))
在代码中使用MAX宏时,如int result = MAX(x,y);,预处理器会将其替换为((x)>(y)?(x):(y)),从而实现计算两个数最大值的功能。不过,带参数的宏可能会有一些潜在的副作用,比如多次求值等问题。
3.#ifdef、#ifndef、#endif指令
用途:用于条件编译。#ifdef用于检查某个宏是否已经定义,#ifndef用于检查某个宏是否未定义,它们通常与#endif一起使用,来控制一段代码是否参与编译。
示例:根据是否定义宏来包含不同代码:
//定义一个宏DEBUG
#include<iostream>
#define DEBUG
using namespace std;
//主函数
int main(){
#ifdef DEBUG
cout << "Debugging information is enabled." << endl;
#else
cout << "This is a normal run." << endl;
#endif
return 0;
}
在这个例子中,因为定义了DEBUG宏,所以#ifdef DEBUG条件成立,会输出Debugging information is enabled.。如果没有定义DEBUG宏,就会输出This is a normal run.。这种条件编译在调试代码、根据不同平台或配置包含不同功能代码等场景中非常有用
4.undef指令
用途:用于取消之前定义的宏。
示例:
#define PI 3.14159
//其他代码使用PI
//...
#undef PI
//这里之后PI就不再有之前的定义了
取消定义后,如果再使用PI,可能会导致编译错误(除非在后续代码中重新定义了PI)。这在需要重新定义一个宏或者在某个特定范围后不再使用某个宏时比较有用。
5.#pragma指令
用途:这是一个因编译器而异的指令,用于向编译器传达特定的信息或指令编译器执行某些特定的操作。不同编译器支持不同的#pargma用法。
//禁用4786号警告(VC++中可能是关于名字过长的警告)
#pragma warning(disable : 4786)
这个指令告诉Visuak C++编译器禁用编号为4786的警告。在其他编译器中,#pargma的用法可能不同,比如在GCC中可以用于控制代码的对齐等