命名空间
概念
命名空间是一个声明性区域,为其内部的标识符(类型、函数和变量等的名称)提供一个范围。
解释
在理解命名空间之前,我们先看一下这个代码
#include<stdio.h>
#include<stdlib.h>
int rand=0;
int main()
{
printf("%d\n",rand);
return 0;
}
这个代码在运行时会报错:"rand"重定义
为什么会报错?
在预处理时,会进行头文件展开,在stdlib头文件种包含rand()函数,rand()函数属于全局范围,在全局范围中还有一个全局变量rand,在同一域内有多个同一变量,在这种情况下就会发生命名冲突
如何解决?
C++中引入了命名空间
命名空间定义
定义命名空间,需要使用到namespace关键字,后面跟命名空间的名字,然后接一对{}即可,{}中即为命名空间的成员。
注意:
1、命名空间只能在全局定义
2、在命名空间内的变量还是全局变量,生命周期不变
定义命名空间的三种方式
//1、普通的命名空间
namespace byld //byld为命名空间的名称
{
//命名空间中的内容,既可以定义变量,也可以定义函数
int rand=0;//将rand关到一个空间
}
//2、命名空间可以嵌套
namespace byte
{
int a=10;
namespace data
{
int b=20;
}
}
//3、同一个工程中允许存在多个相同名称的命名空间,编译器在编译时会合成同一个命名空间中。
//可能会有冲突(嵌套命名空间解决)(?)
namespace byld
{
int d=30;
}
命名空间使用
如何使用命名空间?
使用::(域作用限定符)
namespace byld
{
int a = 20;
}
int a = 30;
int main()
{
printf("%d\n", ::a);//访问全局变量
printf("%d\n", byld::a);//访问byld域内的a变量
return 0;
}
指定命名空间麻烦?
两种解决方式
1、
using namespace byte;//把byte这个命名空间定义的东西放出来
2、
using namspace bit::f;//只放f出来
比如:
namespace byld
{
int a = 30;
}
namespace byte
{
int b = 20;
int c = 10;
}
using namespace byld;
using byte::b;
int main()
{
printf("%d\n", a);//30
printf("%d\n", b);//20
printf("%d\n", byte::c);//10
}
注意:
1、如果是嵌套命名空间,要展开内部的命名空间,需要先展开外部的命名空间
比如:
namespace byte
{
int a = 10;
namespace data
{
int b = 20;
}
}
using namespace byte;
using namespace data;
int main()
{
printf("%d\n", b);//打印20
return 0;
}
2、打开命名空间后,依旧可以采用命名空间名::变量的形式访问
namespace byld
{
int a = 30;
}
using namespace byld;
int main()
{
printf("%d\n", byld::a);//打印30
}
3、如果打开命名空间,有可能会导致命名冲突
比如:
namespace byld
{
int a = 30;
}
namespace byte
{
int a = 10;
namespace data
{
int b = 20;
}
}
using namespace byte;
using namespace byld;
int main()
{
printf("%d\n", byte::a);//打印10
printf("%d\n", byld::a);//打印30
printf("%d\n", a);//错误,命名冲突
return 0;
}
C++输入&输出
首先,我们先认识几个运算符
//流提取运算符
cin
//流插入运算符
cout
//换行
endl =='\n'(endl就相当于'\n')
举例
#include<iostream>
using namespace std;/std是封c++标准库的命名空间
int main()
{
cout<<"Hello World!"<<endl;
return 0;
}
说明:
1、 使用 cout 标准输出(控制台)和 cin 标准输入(键盘)时,必须包含 < iostream > 头文件以及std标准命名空间。
注意:早期标准库将所有功能在全局域中实现,声明在.h后缀的头文件中,使用时只需包含对应头文件 即可,后来将其实现在std命名空间下,为了和C头文件区分,也为了正确使用命名空间,规定C++头文 件不带.h;旧编译器(vc 6.0)中还支持格式,后续编译器已不支持,因此推荐使用 + std 的方式。
2、使用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;
}
缺省函数
全缺省参数
void TestFunc(int a = 10, int b = 20, int c = 30)
{
cout<<"a = "<<a<<endl;
cout<<"b = "<<b<<endl;
cout<<"c = "<<c<<endl;
}
半缺省参数
void TestFunc(int a, int b = 10, int c = 20)
{
cout<<"a = "<<a<<endl;
cout<<"b = "<<b<<endl;
cout<<"c = "<<c<<endl;
}
注意:
1、半缺省参数必须从右往左缺省,并且连续
2、缺省参数不能在函数声明和定义中同时出现
缺省函数声明给,定义给(错误)
3、缺省值必须是常量或者全局变量
4、C语言不支持
函数重载
概念
函数重载:是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的 形参列表(参数个数 或 类型 或 顺序)必须不同,常用来处理实现功能类似数据类型不同的问题
注意:
返回值不同是无法构成重载的(调用时无法区分调用的是谁?)
比如:
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.0, 20.0);
Add(10L, 20L);
return 0;
}
问:下面两个函数属于函数重载吗?
short Add(short left, short right)
{
return left+right;
}
int Add(short left, short right)
{
return left+right;
}
名字修饰
为什么C++支持函数重载?而C语言不支持函数重载?
这个问题,我们需要了解在C/C++中,一个程序要运行起来,需要经历以下几个阶段:预处理,编译,汇编,链接
编译链接的过程: f.h f.cpp test.cpp
1、预处理 – 头文件的展开、宏替换、条件编译、去掉注释
f.i test.i
2、编译 – 检查语法,生成汇编代码(语法错误)
f.s test.s
3、汇编 – 把汇编代码翻译成二进制的机器码
f.o test.o
4、链接 – 找调用函数的地址,链接对应上,合并到一起
f.o,函数调用指令,符号表(函数名,函数地址的映射)
链接错误:在函数表中找不到对应的
为什么C语言不支持?
因为在符号表中,直接用的是函数名,所以如果采用重载函数的话会导致冲突
为什么C++支持?
因为在函数表中,采用的是经过修饰的函数名
函数名修饰:_Z函数名长度 函数名 类型首字母(这也是为什么重载函数不能只以返回回值类型不同)
extern"C"
C 可以调用C/CPP静态库/动态库
CPP 可以调用C/CPP静态库/动态库
C 调用CPP静态库/动态库
CPP 调用C静态库/动态库
在这两种情况下,不能直接调,会找不到(C直接按函数名找,C++会按修饰过的函数名找)
1、CPP如何调用C静态库/动态库时?
1、链接Stack_C.lib
2、
extern "C"
{
#include"../Stack_C/Stack.h"
}
1. stack.h的内容会在{}内展开
extern "C"表示在{}内的函数在链接时,以C的方式链接(按C的方式生成符号表,按C的方式查找)
2、C如何调用CPP静态库/动态库
1、链接Stack_CPP.lib (在Stack_CPP 中不能使用重载函数)
2、
//条件编译
#ifdef __cplusplus
#define EXTERN_C extern"C"
#else
#define EXTERN_C extern"C"
#endif
typedef int STDataType;
typedef struct Stack
{
STDataType* val;
int top;
int capacity;
}Stack;
EXTERN_C void StackIniti(Stack* ps);
EXTERN_C void StackPush(Stack* ps, STDataType data);
// 出栈
EXTERN_C void StackPop(Stack* ps);
// 获取栈顶元素
EXTERN_C STDataType StackTop(Stack* ps);
// 获取栈中有效元素个数
EXTERN_C int StackSize(Stack* ps);
// 检测栈是否为空,如果为空返回非零结果,如果不为空返回0
EXTERN_C bool StackEmpty(Stack* ps);
// 销毁栈
EXTERN_C void StackDestroy(Stack* ps);
引用
概念
引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。
类型&引用变量名(对象名)=引用实体
void Test()
{
int a = 10;
int& ra = a;
//&a==&ra
printf("%p\n", &a);
printf("%p\n", &ra);
}
注意:引用类型必须和引用实体是同中类型的
引用特性
1、引用必须在定义时初始化
2、一个变量可以有多个引用
3、一个引用一旦引用一个实体,就不能引用其他实体
常引用
在常引用中,涉及到取别名原则
对原引用变量,权限只能缩小,不能放大
比如:
int main()
{
const int x=10;
int& y = x;//放大(错误)
const int& y=x;//不变
int c=30;
const int& d=c;//缩小
//对常量取别名
int &a=10;(错误)
const int&c=20;(正确)
return 0;
}
接下来看如下代码:
问:const int& e=d 为什么正确
int main()
{
double d=2.2;
int f=d;//隐式类型转换(临时变量)
//在隐式转换中,会截取double的整数部分给到中间变量,中间变量再给到f
const int& e=d;//e是d的临时变量的别名,这里需要用常引用接受,因为临时变量具有常性
return 0;
}
由谁充当临时变量
1、寄存器(小) 2、(例)在main栈区内开一块空间(大)
引用使用场景
1、 做参数
void Swap(int& left, int& right)
{
int temp = left;
left = right;
right = temp;
}
2、作返回值
在了解引用做返回值之前,我们需要知道函数返回的是一个临时变量,在函数名前的返回值类型是临时变量的类型
比如:
//1、返回值不是引用
int add(int a,int b)
{
int n = a + b;
return n;
}//在add函数结束后,n会被销毁,所以会把n的内容拷贝给临时变量,实际上返回的是临时变量
//2、返回值是引用
int& mult(int a,int b)
{
int c= a * b;
return c;
}//mult函数的返回值类型为int&,返回c的引用,不会发生拷贝,但是在mult函数结束后,c会被销毁,在主函数中调用,会形成非法访问。所以不能在这种情况使用引用作返回值。
int main()
{
int a=1,int b=2;
printf("%d\n",add(1,2));
printf("%d\n",mult(1,2));//非法访问
return 0;
}
注意:如果函数返回时,出了函数作用域,如果返回对象还在,可以用引用返回(比如static修饰的变量)
引用和指针
int main()
{
int a=10;
//语法角度:ra是a的别名,没有额外开空间
//底层角度:他们是一样的方式实现的
int& ra=a;
ra=20;
//语法角度:pa存储a的地址,pa开了4/8byte空间
//底层角度:他们是一样的方式实现的(转化成一样的汇编代码)
int* pa=a;
*pa=20;
return 0;
}
内联函数
概念
以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数压栈的开销, 内联函数提升程序运行的效率。
为什么C++要出inline?
1、解决宏晦涩难懂,容易写错
2、宏不支持调试
注意:
debug不展开;release展开
内联函数优点:
1、debug支持调试
2、不容易写错
特性
1、inline是一种以空间换时间的做法,省去调用函数额开销。所以代码很长或者有循环/递归的函数不适宜 使用作为内联函数。
2、inline对于编译器而言只是一个建议,编译器会自动优化,如果定义为inline的函数体内有循环/递归等,编译器优化时会忽略掉内联。
3、 inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会 找不到。
auto关键字
简介
C++11中,标准委员会赋予了auto全新的含义即:auto不再是一个存储类型指示符,而是作为一个新的类型 指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得。
auto使用
1、
#include<iostream>
using namespace std;
int TestAuto()
{
return 10;
}
int main()
{
int a=10;
auto b=&a;//auto根据=右的类型推导出b的类型(自动推导)
auto c='a';
auto d=TestAuto();
//typeid(b).name() b变量的类型
cout<<typeid(b).name()<<endl;//打印int*
cout<<typeid(c).name()<<endl;//打印char
cout<<typeid(d).name()<<endl;//打印int
return 0;
}
2、auto与指针和引用结合起来使用 用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加&
int main()
{
int x=10;
auto a=&x;
auto* b=&x;
auto* d=x;//错误,d已经明确类型是指针,x必须为地址
auto& c =x;
cout<<typeid(b).name()<<endl;//打印变量的类型
cout<<typeid(c).name()<<endl;
cout<<typeid(d).name()<<endl;
*a=20;
*b=30;
c=40;
return 0;
}
3、在同一行定义多个变量 ,当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量。
int main()
{
int x=10,y=20;
double z=3.14;
auto a=x,b=y;//正确
auto c=x,z=3.14;//错误
return 0;
}
auto使用注意事项:
2、auto不能做参数
3、auto必须初始化
4、auto不能做返回值
5、auto在实际中最常见的优势用法就是跟以后会讲到的C++11提供的新式for循环,还有lambda表达式等进行配合使用。
基于范围的for循环(C++11)
简介
C++11中 引入了基于范围的for循环。for循环后的括号由冒号“ :”分为两部分:第一部分是范围内用于迭代的变量, 第二部分则表示被迭代的范围。
比如:
void TestFor()
{
int array[] = { 1, 2, 3, 4, 5 };
for(auto& e : array)
e *= 2;
for(auto e : array)
cout << e << " ";//打印2 4 6 8 10
return 0;
}
nullptr(关键字)
空指针有三种表达方式
int main()
{
int*a=NULL;//1
int*b=0;//2
int*c=nullptr;//3
return 0;
}
在这三种方式中,1,2是不规范的,因为NULL是一个宏,本质上和0是一样的
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
如果使用不规范的写法就会导致与本意不服:
void f(int)
{
cout<<"f(int)"<<endl;
}
void f(int*)
{
cout<<"f(int*)"<<endl;
}
int main()
{
f(0);//打印f(int)
f(NULL);打印f(int)
f(int*(NULL));//打印f(int*)
f(nullptr);//打印f(int*)
return 0;
}
注意:
1、在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为新关键字引入的。
2、在C++11中,sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同。
3、 为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr。
|