文章目录
- C++的常用关键字
- C++的输入输出
- C++支持重载的原理------名字修饰
- windows下的名字修饰规则
- 引用
- 引用的概念
- 引用的特点
- 使用场景
- 做参数
- 做返回值
- 常引用
C++的常用关键字
C++的常用关键字一共有63个,其中包括有C语言的关键字,这些关键字我们在后面的文章中都会讲到,大家可以在以后的文章中看到这些关键字,所以没有必要在这里记住这些关键字。这里只是一个汇总。
C++的输入输出
我们来写一段每个程序员在学习一门语言时刚开始就会写的一段代码,那就是hello world。
#include <iostream>
using namespace std;
int main()
{
cout << "hello world" << endl;
return 0;
}
我们来对这个代码段做一些解释
- 使用cout标准输出对象(控制台)和cin标准输入对象(键盘)时,必须包含< iostream >头文件
以及按命名空间使用方法使用std。- cout和cin是全局的流对象,endl是特殊的C++符号,表示换行输出,他们都包含在包含<
iostream >头文件中。- <<是流插入运算符,>>是流提取运算符。
- 使用C++输入输出更方便,不需要像printf/scanf输入输出时那样,需要手动控制格式。
C++的输入输出可以自动识别变量类型。- 实际上cout和cin分别是ostream和istream类型的对象,>>和<<也涉及运算符重载等知识,
这些知识我们我们后续才会学习,所以我们这里只是简单学习他们的使用。后面我们还有有
一个章节更深入的学习IO流用法及原理。
注意:早期标准库将所有功能在全局域中实现,声明在.h后缀的头文件中,使用时只需包含对应
头文件即可,后来将其实现在std命名空间下,为了和C头文件区分,也为了正确使用命名空间,
规定C++头文件不带.h;旧编译器(vc 6.0)中还支持<iostream.h>格式,后续编译器已不支持,因
此推荐使用<iostream>
+std的方式
#include <iostream>
using namespace std;
int main()
{
//可以自动识别变量的类型
cout << "hello world" << endl;
return 0;
}
C++支持重载的原理------名字修饰
首先我们需要思考一个问题,为什么C++支持重载而C语言却不支持重载?
我们需要了解 一个C++程序时如何运行起来的,这里和C语言是相应的。
需要经过 :
预处理 头文件展开/宏替换/去掉注释/条件编译 生成 .i 文件
编译 检查语法,生成汇编代码,生成 .s文件
汇编 将汇编代码生成二进制机器码,生成 .o文件
链接 合并链接,生成可执行程序 exe文件。
C语言预处理阶段的介绍
在这里我们需要注意的是,在编译阶段编译器会生成符号表,符号表的内容为函数名+函数地址的映射,这可以使我们方便的跳转到函数体执行函数体的语句,但是在C++中,引入了重载函数,这就需要在符号表中添加一些规则使得相同函数名的函数可以被区分出来。C++把这些规则称为名字修饰(name Mangling),我们可以通过vs编译器来验证这一点:
windows下的名字修饰规则
需要注意的是:返回值不同是没有办法构成重载的,因为调用时编译器没有办法区分。
我们来回答上面的问题:由于C语言没有函数名修饰规则,所以C语言不支持函数重载。
引用
引用的概念
引用不是新定义了一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。
通俗来讲就是调皮的同学给取的外号。
引用的定义方式:
引用的特点
- 引用在定义时必须初始化
- 一个变量可以有多个引用
- 引用一旦引用一个实体,再不能引用其他实体
使用场景
做参数
//以二叉树的前序遍历为例,我们需要一个变量来遍历数组,如果我们在函数里面定义i的话会导致
//函数每次递归调用都会为i建立栈帧,这显然不符合我们的要求,我们需要在外面定义一个i把它的地址传进去,但是使用引用我们就不用传地址了,大大提高了效率。
typedef struct TreeNode {
int val;
struct TreeNode* left;
struct TreeNode* right;
}* TreeNode;
int BTreeSize(TreeNode& root)
{
if (root == NULL)
{
return 0;
}
int lcount = BTreeSize(root->left);
int rcount = BTreeSize(root->right);
return lcount + rcount + 1;
}
void _preorderTraversal(TreeNode& root, int* a, int& ri)
{
if (root == NULL)
{
return;
}
a[(ri)++] = root->val;
_preorderTraversal(root->left, a, ri);
_preorderTraversal(root->right, a, ri);
}
int* preorderTraversal(TreeNode& root, int* returnSize) {
*returnSize = BTreeSize(root);
//malloc一个数组
int* a = (int*)malloc(sizeof(int) * (*returnSize));
//执行前序遍历
//下标
int i = 0;
_preorderTraversal(root, a, i);
return a;
}
做返回值
int Add(int a)
{
int n = a;
return n;
}
//函数的返回值为引用类型
int& Add(int a, int b)
{
int c = a + b;
return c; //我们不提倡这么做
}
int main()
{
int ret = Add(5, 5);
cout << ret << endl; //打印10
cout << ret << endl;
cout << Add(5) << endl;
cout << Add(5)<< endl;
return 0;
}
解释:
结论:
如果函数返回时,出了函数作用域,如果返回对象还在(还没还给操作系统),则可以使用引用返回,如果已经还给操做系统了,则必须使用传值返回。
使用案例,通过引用修改数组空间的值。
常引用
在这里只需要注意,权限可以缩小和平移但是不能放大。
int main()
{
const int a = 10;
//权限被放大会报错
//int& b = a;
//权限平移
const int& b = a;
//权限缩小
int x = 10;
const int& c = x;
//这是可以的
int n = 10;
double f = n;
//这里会产生临时变量,而临时变量具有常性
//如果不加const会产生权限放大的问题。加上
//const就是权限平移
double d = 10.0;
const int& m = d;
return 0;
}
我们下一篇再见!