文章目录
C语言是面向过程的语言,而C++是在C基础上发展起来的面向对象的语言。C++补充了C语言语法的不足,以及对C语言设计不合理的地方进行了优化。在哪方面进行了优化呢?接下来我们将一起学习。
⒈关键字
C++总计63个关键字,C语言32个关键字。
⒉命名空间
⑴为什么会引入命名空间
在C/C++中,变量、函数和类都是大量存在的,并且都将存在于全局作用域中,可能会导致很多冲突。
下面这个代码能正常运行
#include<stdio.h>
int rand = 10;
int main()
{
printf("%d\n", rand);
return 0;
}
但如果给这个代码新加一个头文件stdlib.h便会报错,结果如下:
#include<stdio.h>
#include<stdlib.h>
int rand = 10;
int main()
{
printf("%d\n", rand);
return 0;
}
这是因为头文件stdlib.h中有一个用于产生随机数的函数rand,因为同一个域内不能定义同名变量,这样就区分不开名字相同的变量和函数了,产生了命名冲突。C++使用命名空间则是为了对标识符的名称进行本地化,以避免命名冲突或名字污染,,namespace关键字的出现就是针对这种问题的。
C++中有四种域:全局域、局部域、命名空间域、类域。
使用:访问全局域时在变量前加域作用限定符::,访问局部变量不用指定,访问命名空间空间域在::前1加域名,访问类域在::前加类名。
命名空间内的变量仍是全局的,它只是对全局冲突变量的隔离,不影响变量的生命周期,只影响访问。
编译器的搜索原则:(1)不指定作用域时:先在当前局部域搜索,没有再去全局域搜索;(2)指定作用域时直接去指定域搜索
⑵命名空间如何定义
定义命名空间时,需要使用到namespace关键字,后跟命名空间名,再加一对{},将变量写入里面就可以了。
//命名空间内可以定义变量、函数、结构体
namespace A
{
int a = 1;
int Add(int left,int right)
{
return left + right;
}
struct Node
{
int val;
struct Node* next;
};
.........
}
那么要修改上述代码就可以用以下方法解决了
#include<stdio.h>
#include<stdlib.h>
namespace A
{
int rand = 10;
}
int main()
{
printf("%d\n", A::rand);
return 0;
}
命名空间的其他两种定义方法:
命名空间可以嵌套定义
//test.cpp
namespace B
{
int b = 1;
int Add(int left,int right)
{
return left + right;
}
namespace C
{
int c = 1;
int Sub(int left,int right)
{
return left - right;
}
.........
}
同一个工程允许存在多个名称相同的命名空间,编译后这几个空间的变量会放在一起.
//test.h
namespace B
{
int Mul(int left,int right)
{
return left * right;
}
}
注意:一个命名空间就定义了一个新的作用域,命名空间中的所有内容都局限于该命名空间中
⑶命名空间如何使用
定义了一个命名空间,那么命名空间中的成员该如何使用呢?
看一下下面的代码是否正确?
namespace A
{
int a = 0;
int b = 1;
int Add(int left ,int right)
{
return left + right;
}
struct Node
{
int val;
struct Node* nexxt;
};
}
int main()
{
struct Node phead;
printf("%d\n",a);
printf("%d\n",Add(1,2));
return 0;
}
以上对变量的使用正确吗?
不正确,因为变量均未指定,按照编译器的搜索规则,先后在局部域和全局域搜索,都没有这些变量。
编译报错:
error C2065: “a”: 未声明的标识符
error C3861: “Add”: 找不到标识符
error C2079: “phead”使用未定义的 struct“main::Node”
那么应该怎么改呢?
①加命名空间名称及作用域限定符(指定使用)
如果是命名空间内的变量
printf(“%d\n”,a); --------------> printf(“%d\n”,A::a);
如果是使用命名空间内的函数
printf(“%d\n”,Add(1,2));----------->printf(“%d\n”,A::Add(1,2));
如果是使用命名空间内的结构体,与变量和函数不同,如下
struct Node phead; ----------> struct A:: Node phead;
②使用using将命名空间中某个成员引入(展开某一个)
using A::b
int main()
{
printf("%d\n",A::a);
printf("%d\n",b; //将A中变量b引入,便可直接使用,不用加::
return 0;
}
③使用using namespace命名空间名称引入(展开命名空间)
using namespace std; std是所有C++库的命名空间
即展开命名空间。
头文件展开和命名空间的展开一样吗
头文件展开:拷贝头文件内容
命名空间展开:公开声明,打开访问权限
using namespace A;
int main()
{
printf("%d\n",b);
Add(1,2);
return 0;
}
⒊C++输入&输出
C++兼容C,所以在C++中,C语言的一套输入输出库我们仍然能使用,但是C++又增加了一套新的、使用更简单的输入输出库,即iostream库。
以下是一个标准的C++实现语句。
#include<iostream>
using namespace std;
int main()
{
cout << "Hello World!!" << endl;
return 0;
}
说明:
①iostream库包含两个基础类型istream和ostream。cout是一个ostream类型的对象,称为标准输出对象,cin是一个istream类型的对象,称为标准输入对象,在命名空间std内定义。使用时必须包含头文件以及按命名空间使用方法使用std。
②cout和cin是全局的流对象,endl是特殊的C++符合,表示换行输出,它们都包含在头文件中。
③<<是左移运算符,当于cout搭配时是流插入运算符,>>是右移运算符,与cin搭配时称为流提取运算符。
④C++能自动识别变量类型,不需要像printf/scanf那样手动控制格式。
⒋缺省参数(默认参数)
⑴缺省的概念
缺省参数是声明或定义函数时为函数的参数指定一个缺省值。在调用该函数时,如果没有指定实参则采用该形参的缺省值,否则使用指定的实参。
void Func(int a = 0)
{
cout << a << endl;
}
int main()
{
Func(); //没有传参
Func(5); //传参
return 0;
}
执行结果如下:
⑵缺省的分类
①全缺省参数:给每个函数参数都给一个缺省值
//定义
void Func(int a = 10, int b = 20, int c = 30)
{
cout<<"a = "<<a<<endl;
cout<<"b = "<<b<<endl;
cout<<"c = "<<c<<endl;
}
int main()
{ //调用时有四种情况,不能跳着传参,比如Func(1, ,3);
Func(1,2,3); //结果是1 2 3
Func(1,2); //结果是1 2 30
Func(1); //结果是1 20 30
Func(); //结果是10 20 30
return 0;
}
②半缺省参数:不是说只给一半的参数给缺省值
void Func(int a, int b = 20, int c = 30)
{
cout<<"a = "<<a<<endl;
cout<<"b = "<<b<<endl;
cout<<"c = "<<c<<endl;
}
注意事项:
·1.半缺省参数必须从右往左依次给出,不能间隔
·2.传参时是从左往右传
·3.缺省参数不能在函数声明和定义中同时出现(在声明中给出),防止声明和定义分离时编译阶段报错
·4.缺省值必须是常量或全局变量
·5.C语言不支持
⒌函数重载
⑴函数重载概念
C语言不允许同名函数存在,C++允许在同一作用域中声明几个功能类似的同名函数。这些同名函数的形参列表不同(参数的个数、类型、顺序不同),与返回值类型无关。
①参数类型不同
int Add(int left ,int right)
{
cout << "int Add(int left ,int right)" << endl;
return left + right;
}
double Add(double left ,double right)
{
cout << "double Add(double left ,double right)" << endl;
return left + right;
}
②参数个数不同
void f()
{
cout << "f()" << endl;
}
void f(int a)
{
cout << "f(int a)" << endl;
}
③参数类型顺序不同
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;
}
int main()
{
Add(10,20);
Add(1.1,2.2);
f();
f(2);
f(10,'a');
f('a',10);
return 0;
}
⑵C语言为什么不支持重载,而C++又为什么支持重载?
这就涉及到了程序的编译与连接过程以及函数名修饰规则。
C语言不支持重载,因为在链接时,直接用函数名去找地址,有同名函数,就无法区分
C++如何支持的呢?函数名修饰规则,名字中引入参数类型,各个编译器自己实现了一套。
①程序的编译与链接
程序编译为可执行文件的过程
分为四个阶段:预处理、编译、汇编、链接。下图是各阶段的作用和各阶段文件的类型。
以栈为例,有三个文件,Stack.h是定义栈结构和声明函数的文件,Stack.cpp是定义函数的文件,Test.cpp是用来测试函数功能的文件。
预处理阶段会生成Stack.i和Test.i这两个文件,因为预处理阶段在包含头文件的这两个文件内会Stack.h会被展开,宏被替换,注释被去掉;编译阶段,检查语法,生成Stack.s和Test.s文件;汇编阶段会进行将汇编代码转化为机器指令,生成一个符号表(符号表里有函数名和函数地址),并生成Stack.o和Test.o文件;最后在链接阶段,会将符号表合并和重定位,将Stack.o和Test.o链接成一个可执行程序a.out。
用函数名在符号表中查找函数的地址,有函数定义才有函数地址,否则报错。假设有函数定义,便找到了(2)。
②函数名修饰规则
以下面的Add函数与func函数为例,我们一起来看一下Linux系统中gcc和g++的函数名修饰是怎样的
//测试程序
int Add(int a ,int b)
{
return a + b;
}
void func(int a ,double b ,int* p)
{}
int main()
{
Add(1, 2);
func(1, 2, 0);
return 0;
❶采用C语言编译器编译后结果
结论:在Linux下,采用gcc编译完成后,函数名字的修饰没有发生改变。回答了C语言为什么不支持重载。
❷采用C++编译器编译后结果
结论:在Linux下,采用g++编译完成后,函数名字的修饰发生改变,编译器将函数参数类型信息添加到修改后的名字中。回答了C++为什么支持重载。
在Linux环境下,C++的函数名修饰规则为【_Z + 函数长度 + 函数名 + 类型首字母】
了解一下在Windows环境下,C++的函数名修饰规则:
格式:【? + 函数名 + @@YA + 返回值 + 参数1 + 参数2 +@Z】
int类型对应的是字母H,void类型对应的是字母X,double类型对应的是字母N。
扩展:float类型对应的是字母M
今天就分享到这里,以上就是我整理的C++相比C语言优化的几个方面的内容,谢谢观看,有什么不对的地方还请指出~