概述
目录
一、命名空间
1.命名空间的定义
2.命名空间的3种使用方式
二、C++输入输出
三、缺省参数
1.缺省参数定义
2.缺省参数分类
四、函数重载
1.函数重载概念
2.c++支持重载的原因
3.C++中将一个函数按照C的风格来编译
五、内联函数
1.定义
2. 特性
3.为什么不能每个函数都使用inline
4.inline和宏
六、指针空值nullptr
到c++板块啦
一、命名空间
C++中存在大量的变量、函数和类,都存在于全局作用域中,会导致冲突。使用命名空间可以对标识符的名称进行本地化,来避免命名冲突或名字污染,达到名称隔离的目的。
1.命名空间的定义
C++使用namespace关键字定义命名空间,用来解决命名冲突问题,格式为:namespace后面跟命名空间名字,再接一对{ } ,{ }中是命名空间的成员。
(1)命名空间格式:
namespace N1 //N1是命名空间的名称
{
//命名空间的内容,既可以定义变量,也可以定义函数
int a = 0;
int Add(int left, int right)
{
return left + right;
}
}
(2)命名空间可嵌套:
namespace N2 //N2命名空间中嵌套了N3命名空间
{
int a = 0;
int b = 0;
int Add(int left, int right)
{
return left + right;
}
namespace N3
{
int c;
int d;
int sub(int left, int right)
{
return left - right;
}
}
}
(3)同一个工程中允许存在多个同名命名空间,编译器会合成到同一个命名空间中,如:
001-test.cpp中存在N1命名空间
“::”操作符是域解析操作符
#include<iostream>
using namespace std;
namespace N1 //N1是命名空间的名称
{
//命名空间的内容,既可以定义变量,也可以定义函数
int e;
int Add(int left, int right)
{
return left + right;
}
}
int main()
{
cout <<"e="<< N1::e << endl;//endl是全局换行符
return 0;
}
002-test.cpp中也存在N1命名空间
#include<iostream>
namespace N1 //N2命名空间中嵌套了N3命名空间
{
int b = 0;
int c = 8;
namespace N3
{
int c;
int d;
int sub(int left, int right)
{
return left - right;
}
}
}
注意:
(1)同一命名空间不能定义相同的变量名和函数名。
(2)一个命名空间定义了一个新的作用域,命名空间中的所有内容都局限于该命名空间中。
(3)C++为了防止命名冲突,把自己库里面的东西都定义在std命名空间中。
2.命名空间的3种使用方式
(1) 加命名空间名称及作用域限定符(N::a),比较麻烦,每个用到域中成员的地方都得指定成员的域
#include<iostream>
using namespace std;
namespace N
{
int a = 0;
int Add(int left, int right)
{
return left + right;
}
int Sub(int left, int right)
{
return left - right;
}
namespace N1 //定义嵌套命名空间
{
int max(int left, int right)
{
return left > right ? left : right;
}
}
}
int main()
{
cout << "a = " << N::a << endl;
//嵌套命名空间的使用
int b = N::N1::max(3, 4);
cout << "b = " << b << endl;
return 0;
}
(2)是用using将命名空间成员或函数引入(using N::a,访问a时就不需要加访问限定符"::")
#include<iostream>
using namespace std;
namespace N
{
int a = 0;
int Add(int left, int right)
{
return left + right;
}
int Sub(int left, int right)
{
return left - right;
}
}
using N::a;
int main()
{
cout << "a = " << a << endl;
return 0;
}
(3)使用using namespace 命名空间名称引入(using namespace N,访问a时就不需要加访问限定符"::")
#include<iostream>
using namespace std;
namespace N
{
int a = 0;
int Add(int left, int right)
{
return left + right;
}
int Sub(int left, int right)
{
return left - right;
}
}
using namespace N;
int main()
{
cout << "a = " << a << endl;
return 0;
}
对于std命名空间,如果使用using namespace 命名空间名称将std整个引入,那么库里面的东西全都被展开包含到全局了,虽然看起来方便,但是如果自己定义的内容和库冲突了,就没法解决了。所以日常练习可以使用using namespace std的方式引入std命名空间,规范的工程项目中不推荐。
using namespace std;
可以展开常用的命名空间,下面代码只用到打印,只展开cout和endl命名空间来代替展开std:
#include<iostream>
using std::cout;
using std::endl;
namespace N
{
int a = 0;
int Add(int left, int right)
{
return left + right;
}
int Sub(int left, int right)
{
return left - right;
}
}
using N::a;
int main()
{
cout << "a = " << a << endl;
return 0;
}
使用cout和endl时,也可以不使用using nampspace 命名空间,直接指定成员所在的域
#include<iostream>
namespace N
{
int a = 0;
int Add(int left, int right)
{
return left + right;
}
int Sub(int left, int right)
{
return left - right;
}
}
using N::a;
int main()
{
std::cout << "a = " << a << std::endl;
return 0;
}
二、C++输入输出
1.使用cout标准输出(控制台)和cin标准输入(键盘)时,必须要包含< iostream >头文件及std标准命名空间:
#include<iostream>
using namespace std;
2.c++可以自动识别类型,不需要增加数据格式控制。C语言必须要指定数据格式:整型--%d,字符--%c。
#include<iostream>
using namespace std;
int main()
{
int a;
double b;
char c;
cin >> a;
cin >> b >> c;
cout << a << endl;
cout << b << " " << c << endl;
return 0;
}
三、缺省参数
1.缺省参数定义
缺省参数是声明或定义函数时为函数的参数指定一个默认值。在调用该函数时,如果没有指定实参则采用该默认值,否则使用给定的实参。
#include<iostream>
using namespace std;
void TestFunc(int a = 0)
{
cout << a << endl;
}
int main()
{
TestFunc(); //未给定实参
TestFunc(10); //给定实参
return 0;
}
2.缺省参数分类
(1)全缺省参数:在函数声明或定义时为所有参数都指定一个默认值,调用函数时只给传了实参的参数赋值为实参,否则为默认值:
#include<iostream>
using namespace std;
void TestFunc(int a = 0, int b = 10, int c = 20)
{
cout << a << endl;
cout << b << endl;
cout << c << endl;
cout << "n" << endl;
}
int main()
{
TestFunc(3);
TestFunc(3,4);
TestFunc(3, 4, 5);
return 0;
}
(2)半缺省参数:在函数声明或定义时从右向左为部分参数指定一个默认值,且不能间隔给出
#include<iostream>
using namespace std;
//void TestFunc(int a = 10, int b, int c = 20)间隔给出默认值,错误
//void TestFunc(int a = 10, int b, int c)从左向右给出默认值,错误
void TestFunc(int a, int b = 10, int c = 20)
{
cout << a << endl;
cout << b << endl;
cout << c << endl;
cout << "n" << endl;
}
int main()
{
TestFunc(3);
TestFunc(3,4);
TestFunc(3, 4, 5);
return 0;
}
注意:
(1)缺省参数不能在函数声明和定义中同时出现。
(2)缺省值必须是常量或者全局变量
(3)C语言不支持缺省(编译器不支持)
四、函数重载
c语言不允许定义同名函数,但是c++能够通过函数重载定义同名函数
1.函数重载概念
C++中重载函数通常用来命名一组功能相似而数据类型不同的函数:函数重载是指在同一作用域内,一组函数名相同,不同参数列表(不同参数个数 或 不同类型 或 不同顺序)的函数,这组函数被称为重载函数。
函数重载作用:减少了函数名的数量,避免了名字空间的污染,增加了程序可读性。
如下3个Add函数构成重载,它们的参数类型都不相同,参数类型分别是int、double、long
#include<iostream>
int Add(int left, int right)
{
return left + right;
}
double Add(double left, double right)
{
return left + right;
}
long Add(long left, long right)
{
return left + right;
}
int main()
{
Add(10, 20);
Add(10.3, 20.5);
Add(10L, 20L);
return 0;
}
注意1:函数是否重载取决于同名函数参数列表是否相同,函数重载和函数返回值类型没有关系。
如下两个函数不是函数重载,编译会报错,因为参数列表相同,不构成重载,尽管返回值类型不同,但函数重载定义并不关注返回值类型。
int Add(short left, short right)
{
return left + right;
}
short Add(short left, short right)
{
return left + right;
}
注意2:带缺省参数的函数和原函数不算重载
如下两个函数参数类型、个数、顺序都相同,不构成重载
int Add(int left, int right)
{
return left + right;
}
int Add(int left = 2, int right = 3)
{
return left + right;
}
另外,虽然下面的两个f函数构成重载,但是调用时报错,因为16行对f函数进行调用时,编译器并不知道是该匹配两个参数的f函数还是该匹配带有缺省参数c的f函数,因此会报错。
void f(int a, int b, int c=1)
{
}
void f(int a, int b)
{
}
int main()
{
f(1, 2);
f(1, 2, 3);
return 0;
}
2.c++支持重载的原因
c/c++编译过程:
在linux环境中,创建f.h文件,写如下代码:
创建f.cpp文件,写如下代码:
创建main.c文件,写如下代码:
执行g++ -o cpp f.cpp main.cpp
编译通过,生成cpp可执行文件
执行objdump -S cpp
生成汇编代码:
main.cpp的#include "f.h"包含了f.h文件,f.h文件只是包含了两个add函数的声明,在编译阶段,编译器会认为函数定义在其他地方,而让函数编译通过,等到链接时,再找函数的定义。
链接时,通过函数名即函数地址去其他目标文件中找add的地址,如果找到了就填上add的地址。
根据c++函数名修饰规则,只要参数不同,修饰出来的函数名就不同,这就支持了重载:把参数类型首字母带进函数名中,参数不同,函数签名就不同。
_Z3addii函数名中,_Z是前缀,3是函数的字符长度(add长度为3),ii是两个参数类型int的首字母
同理,_Z3adddd函数名中,_Z是前缀,3是函数的字符长度(add长度为3),dd是两个参数类型double的首字母。
以上也能看出c++函数签名和返回值类型无关。
那么,对于c语言直接拿函数名做名称,无法支持定义两个同名函数
f.h
f.c
main.c
执行 执行g++ -o test f.cpp main.cpp
编译通过,生成test可执行文件
执行objdump -S test
生成汇编代码:
C语言无法支持重载,因为同名函数没办法区分。而C++是通过函数修饰规则来区分,只要参数不同,修饰出来的名字就不一样,这就支持了重载。
3.C++中将一个函数按照C的风格来编译
使用extern "C"可以在C++文件中引用c文件的函数
f.h文件
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int add(int a, int b);
f.c文件
#include "f.h"
int add(int a, int b)
{
return a + b;
}
main.cpp
#include "f.h"
int main()
{
int ret = add(2, 3);
printf("%d",ret);
return 0;
}
程序报错,这是因为当c++调用add函数时,add函数被编译器编译后,函数签名变成了"?add@@YAHHH@Z",链接时,main函数却找不到这个符号。
为了能够让c++调用c文件中实现的函数,需要给该函数加上extern "C",修改f.h文件如下:
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
//写法一
extern "C" int add(int a, int b);
//写法二,能针对多行声明:可将要按照c风格进行编译的所有函数声明全部放在{ }中
//#ifdef __cplusplus
//extern "C"{
//#endif
//int add(int a, int b);
//#ifdef __cplusplus
//};
//#endif
编译成功
为什么加上extern "C"后,就能执行成功呢?
这是由于windows下的编译器会在c语言符号(变量和函数)前加上“_”,即add函数被编译后的符号为_add。因为c语言如果只有函数声明而缺少函数定义,会报缺少"_add"符号:
f.h
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
//extern "C" int add(int a, int b);
#ifdef __cplusplus
extern "C"
#endif
int add(int a, int b);
f.c
#include "f.h"
//int add(int a, int b)
//{
// return a + b;
//}
main.c
#include "f.h"
int main()
{
printf("%d", add(2, 3));
return 0;
}
在VS下,c++文件调用c实现的函数时,编译时,c函数add编译后的符号为"_add"。而在c++的main中,add编译后的符号为"?add@@YAHHH@Z"。两个符号不一致,导致链接失败。而当add在extern "C"中声明后,c++的main中就会将add编译成符号"_add",符号一致,链接成功。
五、内联函数
1.定义
inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数压栈的开销,因此,内联函数能够提升程序运行的效率。
#include<iostream>
int Add(int a, int b)
{
int c = a + b;
return c;
}
int main()
{
int ret = 0;
ret = Add(1, 2);
return 0;
}
VS环境下,F10-调试-窗口-反汇编,发现会call Add
加上inline关键字后,在release模式和debug模式下查看是否存在call Add语句
(1)release模式下,直接查看编译器生成的汇编代码中是否存在call Add
(2)debug模式下,编译器默认不会对代码进行优化,需要对编译器进行设置,否则不会展开:调试-调试属性-配置属性-C/C++-常规-调试信息格式-程序数据库(/Zi)
调试-调试属性-配置属性-C/C++-常规-内联函数扩展-只适用于_inline(/Ob1)
查看反汇编结果,没有call Add,直接使用了add指令:
2. 特性
(1)inline是一种以空间换时间的做法,省去调用函数额开销。所以代码很长或者有循环/递归的函数不适宜使用作为内联函数。
(2)inline对于编译器而言只是一个建议,编译器会自动优化,如果定义为inline的函数体内有循环/递归等等,编译器优化时会忽略掉内联。
(3)inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到
f.c
#pragma once
#include <iostream>
using namespace std;
inline void f(int i);
f.cpp
#include "f.h"
void f(int i)
{
cout << i << endl;
}
main.cpp
#include "f.h"
int main()
{
f(10);
return 0;
}
编译报错,链接错误,如下所示。将inline修饰的函数声明和定义放在.h或源文件就可以了。
3.为什么不能每个函数都使用inline
假如有一个函数,进行编译汇编后有100条指令,如果有10个地方调用
(1)该函数不加inline,建立栈帧,总计100 + 10 = 110条指令
(2)该函数加inline,不建立栈帧,每个地方都展开,总计100 * 10 = 1000条指令
从110条指令变成1000条指令,虽然建立栈帧不一定比不建立栈帧慢,但是编译出来的可执行程序变大,安装软件的人体验变差,执行程序内存消耗变多。
4.inline和宏
C语言为了避免小函数建帧的消耗,提供宏函数支持,在预处理阶段展开。既然C语言已经解决了,为什么C++还要提供inline函数?
因为宏有以下缺点:
(1)不支持调试(编译阶段进行了替换)
(2)宏函数语法复杂,容易出错
(3)没有类型安全检查
c++使用三种法师代替宏:枚举、const常量定义、inline内联函数。
六、指针空值nullptr
NULL实际是一个宏,NULL的定义:
NULL可能被定义为字面常量0,或者被定义为无类型指针(void*)的常量。但对于如下代码:
void f(int)
{
cout<<"f(int)"<<endl;
}
void f(int*)
{
cout<<"f(int*)"<<endl;
}
int main()
{
f(NULL);
f((int*)NULL);
return 0;
}
在c++中,NULL被定义为0,想用f(NULL)用空指针NULL作为参数,但是根据打印结果,发现 f(NULL)调用的是参数为int的f函数:
这是因为C++中,字面常量0既可以是一个整形数字,也可以是无类型的指针(void*)常量,但是编译器默认情况下将其看成是一个整形常量,如果要将其按照指针方式来使用,必须对其进行强转(void *)0。 为此,C++11新增了关键字nullptr,用于表示空指针。为向后兼容,C++11仍然可以使用0来表示空指针,因此表达式nullptr=0为true,但使用nullptr提供了更高的类型安全。
注意:
1. 在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为新关键字引入的。
2.nullptr是指针类型,不能转化为整形类型,可以隐式转换为任意类型的指针,也可以隐式转换为bool类型代表false。
3. 在C++11中,sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同。
4. 为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr。
最后
以上就是怕孤单世界为你收集整理的【C++】-- 命名空间、函数重载、内联函数一、命名空间二、C++输入输出 三、缺省参数四、函数重载 五、内联函数六、指针空值nullptr 的全部内容,希望文章能够帮你解决【C++】-- 命名空间、函数重载、内联函数一、命名空间二、C++输入输出 三、缺省参数四、函数重载 五、内联函数六、指针空值nullptr 所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复