写在前面
本篇笔记作为C++的开篇笔记,主要是讲解C++关键字(C++98)连带一点点(C++11)的知识。掌握的C++新语法新特性,当然C++是兼容C的,我们学习C的那套在C++中也是受用。
文章目录
- 写在前面
- 一、命名空间域
- [二、 C++的输入/输出](#二、 C++的输入/输出)
- 三、缺省参数
- 四、函数重载
- 五、C++98关键字
一、命名空间域
在C/C++中,变量、函数和后面要学到的类都是大量存在的,这些变量、函数和类的名称将都存在于全局作用域中,可能会导致很多冲突。使用命名空间的目的是对标识符的名称进行本地化,以避免命名冲突或名字污染 ,
namespace
关键字的出现就是针对这种问题的。
我们在全局中定义一个变量rand
,但是rand函数在stdllib.h
库中已经定义,根据我们学习过的程序的程序编译与链接笔记提到过,在连接中出现相同的变量符号表合并时会报错。
为了避免这种情况C++推出了新的关键字namespace
,命名空间域。这样在全局中,我们就保护了我们自己定义的rand
变量。
1.1、命名空间域的定义与使用
定义命名空间,需要使用到 namespace
关键字 ,后面跟命名空间的名字 ,然后接一对{}
即可,{}
中即为命名空间的成员。
这时候我们运行程序,发现打印结果并不是我们自己创建的全局变量rand
,如下图
这是因为被命名空间域保护 起来的变量外界不能直接访问。
命名空间的使用有三种方式:
- 加命名空间名称及作用域限定符( :: )
cpp
namespace Bucai {
int rand = 10;
int k = 20;
}
int main()
{
printf("%d\n", Bucai::k);
return 0;
}
- 使用using将命名空间中某个成员引入
cpp
namespace Bucai {
int rand = 10;
int k = 20;
}
using Bucai::k;
int main()
{
printf("%d\n", k);
return 0;
}
- 使用
using namespace
命名空间名称 引入 。
这个效果是暴露命名空间域的内容,让外部可以直接访问。但是这和直接 在全局中定义变量 的效果是不一样 的,因为在命名空间域中 会有标识 ,在编译连接中 形成的符号表不会与在全局变量中定义的吻合。
cpp
#include <stdio.h>
#include <stdlib.h>
namespace Bucai {
int rand = 10;
int k = 20;
}
using namespace Bucai;
int main()
{
printf("%d\n", k);
return 0;
}
但是需要注意的是,如果在上面代码中,我们直接使用rand
会,程序会报错,如下图
报出错误是rand
不明确符号 ,不再是之前的重定义 ,所以我们使用using namespace
命名空间名称引入,需要留意直接引入后的结果。
1.2、命名空间域的细节
命名空间域也是域,它与作用域 的细节是相似的,代码块就是作用域的一种表现形式 ,我们使用代码块 来理解命名空间域 会更好,在代码块 中,我们定义的变量等与外界是互不干涉的,而且在代码块中我们使用对应的变量采用的是就近原则,而且在代码块中可以嵌套代码块,在代码块外面访问不了代码块的内容,因为在代码块中的内容出了代码块作用域就结束。
命名空间域我们可以理解为一个有名称的代码块。必须定义在全局中的"代码块",在域中可以随意的定义变量 ,这样外界不会与域中 的变量命有冲突 ,当我们想要使用域中变量 时,可以通过域名+作用域限定符 来完成引用。
命名空间域细节:
- 命名空间中可以定义变量/函数/自定义类型/类
cpp
namespace Bucai {
int rand = 10;
int k = 20;
int Add(int left, int right)
{
return left + right;
}
class MyName {
public:
int age = 18;
};
}
- 命名空间可以嵌套
c
namespace Bucai {
int k = 0;
namespace bbbb {
int age = 18;
}
}
- 同一个工程中允许存在多个相同名称的命名空间,编译器最后会合成同一个命名空间中。
二、 C++的输入/输出
C++兼容C,自然是支持C的标准输入输出的,但是C的标准输入输出有点麻烦,每次都需要程序猿手动标识这个变量是上面类型,需要使用%
什么来进行输出,很麻烦,所以C++推出了一个全新玩法。
cpp
#include<iostream>
// std是C++标准库的命名空间名,C++将标准库的定义实现都放到这个命名空间中
using namespace std;//在平时练习中我们可以直接展开std命名空间域
int main()
{
cout << "Hello world" << endl;
return 0;
}
说明:
- 使用cout标准输出对象(控制台)和cin标准输入对象(键盘)时,必须 包含
<iostream>
头文件 以及按命名空间 使用方法使用std
。 cout
和cin
是全局的流对象 ,endl
是特殊的C++符号,表示换行输出(即C中的\n
) ,他们都包含在包含<iostream>
头文件中。<<
是流插入运算符,>>
是流提取运算符。- 使用C++输入输出更方便 ,不需要 像
printf/scanf
输入输出 时那样,需要手动控制格式 。C++ 的输入输出 可以自动识别变量类型。 - 实际上
cout
和cin
分别是ostream
和istream
类型的对象,>>
和<<
也涉及运算符重载等知识。~后面不才专门写一篇笔记来讲解IO流用法及原理。~ - 在C++中为了兼容C语言 ,在C++ 使用流输入输出时需要检查C语言 的输入输出,从原理的角度 说,C++ 的输入输出效率是比C语言 的输入输出要低的。
注意: 早期标准库将所有功能在全局域中实现,声明在.h后缀的头文件中,使用时只需包含对应头文件即可,后来将其实现在std命名空间下 ,为了和C头文件区分,也为了正确使用命名空间,规定C++头文件不带.h ;旧编译器(vc 6.0)中还支持<iostream.h>格式,后续编译器已不支持,因此推荐使用<iostream>
+std
的方式来使用。
cpp
#include <iostream>
using namespace std;
int main()
{
int a;
double b;
char c;
// 可以自动识别变量的类型
cin >> a;
cin >> b >> c;
cout << endl << a << endl;
cout << b << " " << c<< " " << 12.888 << endl;
return 0;
}
测试运行结果:
2.2、关于std命名空间的使用惯例
std是C++标准库的命名空间
- 在日常练习 中,建议直接
using namespace std
即可,这样就很方便。 - 在项目开发 中,
using namespace std
展开,标准库就全部暴露出来了,但是项目开发中代码较多、规模大,就很容易出现冲突问题。所以建议在项目开发中使用,像std::cout
这样使用是指定命名空间 + using std::cout
展开常用的库对象/类型等方式
三、缺省参数
缺省参数是声明或定义函数时为函数的参数指定一个缺省值。在调用该函数时,如果没有指定实参则采用该形参的缺省值,否则使用指定的实参。
cpp
void Func(int a = 20)
{
cout << a << endl;
}
int main()
{
Func(); // 没有传参时,使用参数的默认值
Func(10); // 传参时,使用指定的实参
return 0;
}
测试运行结果:
在上结果图中,我们也可以看出在函数定义中,我们设计了一个形参,但是形参给了一个缺省值20
,在我们调用函数时,我们没有给形参a
传递实参时,a
就使用缺省值,所以打印20
,在我们有传递实参时,形参就接收实参参数,缺省值就失效了,所以打印10
。
缺省参数类型:
- 全缺省参数
cpp
void Func(int a = 10, int b = 20, int c = 30)
{
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl;
}
- 半缺省参数
- 半缺省参数必须从右往左依次来给出,不能间隔着给
- 缺省参数不能 在函数声明和定义 中同时 出现,如果函数声明和定义分开 ,缺省参数需要在声明中指定。
- 缺省值必须是常量或者全局变量
- C语言不支持(编译器不支持)
cpp
void Func(int a, int b = 10, int c = 20)//从右往左依次指定缺省值
{
cout<<"a = "<<a<<endl;
cout<<"b = "<<b<<endl;
cout<<"c = "<<c<<endl;
}
传参时编译器读取实参是从左往右读取 的,如果不是 从右往左 指定缺省值 ,那么在实参传递时,会出现程序猿意想之外的错误 ,所以编译器会检查 缺省值的给定,若出现缺省值的指定不是从右往左 ,则报错,如下图。
在函数声明与定义分开 的工程中,如果我们把缺省值放在定义中会出现报错:如下程序
cpp
//test.h
void Func(int a, int b, int c);
//tect.c
#include "test.h"
void Func(int a = 10, int b = 20, int c = 30)
{
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl;
}
//main.c
#include "test.h"
int main() {
Func();
return 0;
}
测试结果:
在程序编译与链接笔记中,我们已经知道#include
引用的头文件,最后都是拷贝 头文件内容到当前文件下的。
在test.h
头文件中,我们只声明了没有缺省值的Func
函数,在编译阶段拷贝到工程中,就不是有缺省值的函数,所以报错。
四、函数重载
自然语言中,一个词可以有多重含义,人们可以通过上下文来判断该词真实的含义,即该词被重载了。
比如:以前有一个笑话,国有两个体育项目大家根本不用看,也不用担心。一个是乒乓球,一个是男足。前者是"谁也赢不了 !",后者是"谁也赢不了!"。
谁也赢不了虽然是一样的字,但是意思完全不一样,这就形成了重载,同理,C++中也做出了相似的函数重载。
函数重载: 是函数的一种特殊情况,C++允许在同一作用域中声明几个 功能类似的同名函数 ,要求 这些同名函数 的形参列表 (参数个数 或 类型 或 类型顺序 ) 不同,常用来处理实现功能类似数据类型不同的问题。
我们设计一个交换函数,用来交换变量值:
cpp
void Swap(int* a, int* b) {
int num = *a;
*a = *b;
*b = num;
}
void Swap(double *a, double *b) {
double num = *a;
*a = *b;
*b = num;
}
int main() {
int a = 10, b = 20;
Swap(&a, &b);
cout << "a = " << a << " b = " << b << endl;
double c = 10.12, d = 20.12;
Swap(&c, &d);
cout << "c = " << c << " d = " << d << endl;
return 0;
}
运行结果:
这样我们就完成了不同类型的变量交换,Swap
函数形成了重载,这时C++编译器特有的属性
构成重载的三大要素:
- 形参个数不同
- 形参类型顺序不同
- 形参类型不同
注意:函数的返回值不同是不构成函数重载的!!
4.1、深入了解C++的重载机制
在深入了解之前,我们先认识一下C语言为什么不支持重载,但在此之前我们需要清楚C/C++的程序编译与链接,因为重载机制 的核心 是发生在编译阶段完成的。
在C语言中,我们根据不才写的程序编译与链接笔记可以知道,在程序在经过编译后 我们的函数符号名是不会有改变的。我们以下程序为例:
c
#include <stdio.h>
void Swap(int* a, int* b) {
int num = *a;
*a = *b;
*b = num;
}
int main(){
int a = 10;
int b = 20;
Swap( &a,&b);
printf("%d \n",a);
return 0;
}
我们在Linux环境下查看上面C语言生成的符号表(如下图)
在上图中,可以清晰看出在C语言中 ,函数符号名有且只有一个 ,这样就导致了C语言的编译器不支持重载 ,而C++则推出了全新玩法 :把编译后的函数符号名更改为另一种形式函数符号名,让其实现函数的重载。
在Windows环境下,函数命名太过复杂,不才这里使用 g++
编译器。
我们以上面代码为例:
cpp
#include <stdio.h>
void Swap(int* a, int* b) {
int num = *a;
*a = *b;
*b = num;
}
int main(){
int a = 10;
int b = 20;
Swap( &a,&b);
printf("%d \n",a);
return 0;
}
我们在Linux环境下,查看由g++
编译器编译后所形成的符号表 ,查看 函数符号名 的变化,如下图。
此时C++中的函数,已经不再是单纯的Swap
,而是在Swap
前后增加了新东西。那一前一后的东西需要查看对应编译器的命名规则,如下。
C++的函数符号名命名规则:
- 每个编译器都有自己的函数名修饰规则
- g++编译器 中的函数修饰后变成【_Z+函数长度+函数名+类型首字母】
- Windows下名字修饰规则
根据命名规则 我们可以得出交换函数Swap
在C++形成_Z4SwapPiS_
代表着:
_Z
:固定开头4
:代表着函数名字的长度,Swap
长度4个字符,所以是4
Swap
:代表了函数名Pi
:代表了int*
类型的首字母合体,P
代表是指针,i
代表是整形。
cpp
void f(int a, char b)
{
cout << "f(int a,char b)" << endl;
}
void f(char b, int a)
{
cout << "f(char b, int a)" << endl;
}
void f(int a, double b, char c) {
cout << "f(int a, double b, char c)" << endl;
}
int main()
{
f(10, 'a');
f('a', 10);
f(10, 2.5, 'a');
return 0;
}
我们使用g++编译器,生成符号表查看上面函数f
的符号名,如下图
第一个函数f
:后面的类型是i
与c
,对应形参中的int
与char
;
第二个函数f
:后面的类型是c
与i
,对应形参中的char
与int
;
第三个函数f
:后面的类型是i
、d
与c
,对应形参中的int
、double
与char
;
通过这里就理解了C语言没办法支持重载,因为同名函数没办法区分。而C++是通过函数修饰规则来区分,只要参数不同,修饰出来的名字就不一样,就支持了重载。
有此,我们可以总结 出,函数的重载 只会与形参列表 中的 参数个数 或 类型 或 类型顺序 相关,因为只有这些才控制着函数符号名 。当函数的符号名相同 时,编译器在链接时 候也是会区分不开 ,所以必须保证函数符号名唯一。
五、C++98关键字
C++总计63个关键字(其中包含C语言32个关键字),如下表格
asm | do | if | return | try | continue |
auto | double | inline | short | typedef | for |
bool | dynamic_cast | int | signed | typeid | public |
break | else | long | sizeof | typename | throw |
case | enum | mutable | static | union | wchar_t |
catch | explicit | namespace | static_cast | unsigned | default |
char | export | new | struct | using | friend |
class | extern | operator | switch | virtual | register |
const | false | private | template | void | true |
const_cast | float | protected | this | volatile | while |
delete | goto | reinterpret_cast |
~ps:下级正在紧张制作中,欲知后事如何,请听下回分解~
以上就是本章所有内容。若有勘误请私信不才。万分感激💖💖 如果对大家有用的话,就请多多为我点赞收藏吧~~~💖💖
ps:表情包来自网络,侵删🌹