Hello everybody~欢迎来到C++的世界!接下来我们将从C++入门到精通(bushi),欢迎跟着我一起学习C嘎嘎!
C++是在C的基础之上,容纳进去了面向对象编程思想,并增加了许多有用的库,以及编程范式等。熟悉C语言之后,对C++学习有一定的帮助
本章节主要目标:
- 补充C语言语法的不足,以及C++是如何对C语言设计不合理的地方进行优化的,比如:作用域方面、IO方面、函数方面、指针方面、宏方面等。
- 为后续类和对象学习打基础。
C++关键字
C++总计63个关键字,C语言32个关键字
ps:下面我们只是看一下C++有多少关键字,不对关键字进行具体的讲解。后面我们学到以后再细讲。
命名空间
在C/C++中,变量、函数和后面要学到的类都是大量存在的,这些变量、函数和类的名称将都存在于全局作用域中,可能会导致很多冲突。使用命名空间的目的是对标识符的名称进行本地化,以避免命名冲突或名字污染,namespace关键字的出现就是针对这种问题的
比如我们在这里写一串代码
#include<stdio.h>
//#include<stdlib.h>
int rand=10;
int main()
{
printf("%d",rand);
return 0;
}
我们一开始先不包含stdlib.h头文件,然后我们会发现可以打印rand的值,但是如果我们包含了之后,就会显示名为重命名的错误。这是为什么呢?我们在C语言中使用过,名为rand()的随机数生成函数,于是在这里,rand全局变量与rand()函数发生了冲突。
其实,在C语言中,还存在着很多这样子的问题。比如刚才的命名与库中函数重名的现象,抑或是两个程序员命名同名函数的现象,都是C语言中无法解决的问题。但是我们会发现,局部变量和全局变量同名却是可以的,只不过在局部里局部变量优先,现在我们就要提出域这个词了。
命名空间域
域分为全局域,局部域(默认),命名空间域和类域,而我们接下来要学的就是命名空间域。
域作用限定符
域作用限制符是C++中用于访问特定作用域内元素的符号。在C++中,可以使用双冒号(::)作为域作用限制符,它可以用来指定类中不同的作用域级别。
具体来说,::左边表示的是作用域,右边表示的是访问的成员。在类中使用域作用限制符可以用来区分成员函数、成员变量以及静态成员函数和变量。
此外,域作用限制符还可以用来访问命名空间中的元素,以及调用基类中的成员函数或变量。在子类中,可以使用域作用限制符来调用父类中的覆盖函数。
而这里我们先只讲解其在域中的作用,其在左边没有给作用域时,则默认是全局域。
而如果我们要定义一个命名空间域,切记一定要定义在全局。
并且,命名空间域定义的只是一个域,不会影响到变量的生命周期,只影响访问。而全局域和局部域则都会影响
当我们去寻找一个变量时,编译器首先会从局部域搜索,再就是全局域,如果制定了命名空间域,那么就会去空间域里寻找,这样就解决了c语言中的搜索问题。
这个时候,有同学就会问,如果我们定义了两个同名空间域呢?那两个空间域就会合并,并且如果里面的变量没有出现重复的话,是完全没有问题的。在命名空间域内,我们可以定义很多东西,变量,函数,结构体等等。举个例子:
namespace bit{
int rand=10;
int add(int a,int b){
return a+b;
}
struct Node{
struct Node* next;
int val;
};
}
int main()
{
printf("%d",bit::rand);
int a=bit::add(2,3);
struct bit::Node phead;
return 0;
}
并且,我们在不同文件中都可以使用同名的命名空间,最后它们都会合并成一个
而如果我们不想这么麻烦,还要在变量前面加上符号,我们就可以用这个语句
using namespace 域名;
这样子的话,我们就默认把这个域的访问权限开放了,放到了全局当中,这个文件中哪里都可以使用,而我们以前经常会在C++的代码中看到这句话:
using namespace std;
std实际上就是所有C++库的命名文件,使用它在小型程序中虽然方便,但不建议在大型程序中使用。在大型项目中,多人协作开发时,每个人编写的代码都可能使用不同的命名空间。如果在一个文件中使用“using namespace std”,则可能导致其他人的代码出现不可预期的结果,从而影响整个项目的质量。
在这里,我们总结一下,命名空间的使用方法总共有三种:
- 加命名空间名称及作用限定符 eg,bit::rand
- 使用using将命名空间中某个成员引用
eg. using bit::rand;- 使用using namespace命名空间名称引入
前两种一般都是在外面使用,第三种在练习的时候使用
一般我们在项目中使用第一种,不建议展开命名空间,或者展开常用的几种
当然,我们会发现,在一个公司中的代码,很可能其下放到的命名空间很可能是一样的,那么就还是会有可能发生命名冲突的问题,那么这该如何解决呢?答案是嵌套命名空间,举个例子:
namespace bit{
namespace wt{
void push(int a){
return a;
}
}
namespace bt{
void push(int a){
return 2*a;
}
}
}
这样的话,虽然两个函数同名,但是我们使用的时候就可以区分开来,比如使用第一个push就是bit::wt::push(a);
需要注意的点
我们会注意到,当我们包含了iostream头文件之后,我们却无法使用一些里面的如cin,cout之类的符号,这是为什么呢?其实就跟之前是一样的,举个例子,如果在头文件 myheader.h 中有一个命名空间 MyNamespace,并且定义了一个类 MyClass,那么在包含了 myheader.h 的源文件中,如果不展开 MyNamespace,则无法直接使用 MyClass 类,而需要使用 MyNamespace::MyClass 进行访问。
所以,包含头文件,与展开命名空间是两个意思,我们需要注意这一点。
C++输入输出
cout使用
我们首先给大家看一段C++输出hello world的代码
#include<iostream>
// std是C++标准库的命名空间名,C++将标准库的定义实现都放到这个命名空间中
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流用法及原理
cin使用
#include<iostream>
using namespace std;
int main()
{
int a;
double b;
char c;
cin >> a;//流提取运算符
cin >> b >> c;
//相当于scanf,但是会自动识别类型,不需要%d这样的标识符
cout << a << endl;//流插入运算符
cout << b << " " << c << endl;//endl跟'\n'是一个作用
return 0;
}
cin和cout的缺点
性能问题:cin和cout在处理大量数据时可能会表现出相对较慢的性能。这主要是因为它们与C的stdio库函数(如scanf和printf)相比,多了一层流缓冲区的处理。cin和cout需要将数据通过流缓冲区来同步C的stdio库函数,这可能会增加一些额外的开销。特别是在处理大量数据或进行频繁输入输出操作时,这种性能差异可能会更加明显。
格式化能力有限:cout的格式化输出能力相对较弱,不如printf那样支持复杂的格式化输出。虽然cout提供了基本的格式化选项,但如果需要更复杂的格式化,可能需要使用其他方法或库来实现。
类型安全问题:与printf相比,cout在编译时会检查参数类型,从而提供了更好的类型安全性。然而,这也可能意味着在某些情况下,cout的灵活性不如printf。例如,如果尝试使用不正确的类型与cout进行交互,它可能会导致编译错误或不可预测的行为。
与C语言兼容性:cin和cout是C++特有的,与C语言的stdio库函数(如scanf和printf)不完全兼容。因此,在需要同时使用C++和C语言的混合编程环境中,可能会遇到一些兼容性问题。例如,混用cout和printf可能会导致缓冲区刷新问题和输出顺序混乱。
缺省参数
缺省参数是声明或定义函数时为函数的参数指定一个缺省值。在调用该函数时,如果没有指定实参则采用该形参的缺省值,否则使用指定的实参。
void Func(int a = 0)
{
cout<<a<<endl;
}
int main()
{
Func(); // 没有传参时,使用参数的默认值
Func(10); // 传参时,使用指定的实参
return 0;
}
很明显,Func()输出的结果是0,而Func(10)的结果则是10
缺省参数分类
全缺省
void Func(int a = 10, int b = 20, int c = 30)
{
cout<<"a = "<<a<<endl;
cout<<"b = "<<b<<endl;
cout<<"c = "<<c<<endl;
}
半缺省
void Func(int a, int b = 10, int c = 20)
{
cout<<"a = "<<a<<endl;
cout<<"b = "<<b<<endl;
cout<<"c = "<<c<<endl;
}
需要注意的是:
- 半缺省参数必须从右往左依次来给出,不能间隔着给(但是如果在使用函数的时候给函数传参还是从左往右依次传)
- 缺省参数不能在函数声明和定义中同时出现(这点很重要),要在声明的地方给
- 缺省值必须是常量或者全局变量
- C语言不支持(编译器不支持)
下面我们讲解一下为什么缺省参数要在声明的地方给,因为在我们调用函数的时候,一开始我们只能找到函数的声明,在后续链接的时候找到它的地址。
我们知道,编译器处理代码是以这样一个顺序来进行的:预处理-》编译-》汇编-》链接,在编译阶段,函数只有声明也可以过,因为只要语法正确就可以,但是在链接部分如果无法找到,那就错误了。而如果缺省参数在定义里面首次出现,那么声明和定义的两个函数参数内容就不匹配,在链接阶段的调用阶段使用缺省参数时就会报错。
而在编译器的报错中,说明的是:函数不接受0个参数,或者是,函数接受的参数过少
函数重载
自然语言中,一个词可以有多重含义,人们可以通过上下文来判断该词真实的含义,即该词被重载了。
比如:在互联网上,有很多原本是褒义的词,如遥遥领先,卧龙凤雏等,都会被人们用来调侃对方
函数重载:是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数 或 类型 或 类型顺序)不同,常用来处理实现功能类似数据类型不同的问题。
#include<iostream>
using namespace std;
void sum(int a, int b) {
cout << a + b << endl;
}
void sum(double a, double b) {
cout << a + b << endl;
}
int main()
{
sum(1, 2);
sum(1.1, 3.2);
return 0;
}
这就是一个例子,另外,需要注意的是,对于参数类型的不同,重要的在于函数的参数列表,而不是函数的返回参数类型
而为什么C++可以重载函数,C语言却不可以呢?这在于C语言不像C++,无法区分同名函数
就像如果我们把上面的两个函数放到C语言里,我们知道声明和地址是分离的,现在有两个名字一模一样的函数,我们怎么知道我们要call 的是那个函数的地址,所以用函数名找地址是无法做到函数重载的,只能改名
而C++有一套自己的函数名修饰规则,名字中引入各种参数类型,各个编译器用其方法实现,比如Linux
首先在函数名前加一个_Z,1是函数名的字节数,f是函数名,而i和c分别是int和char的首字母,这样一来,就可以轻松区别开两个同名函数的区别了,在寻找地址的时候也不会搞错
其实这一部分涉及到了许多编译原理的部分,笔者在之后熟练了之后,会专门做一篇给大家讲解讲解
C++入门环节一到这里就完了,我们下个阶段再见!