在C/C++中,变量,函数和后面要学到的类都是大量存在的,这些变量,函数和类的名称都将存在于全局变量作用域中,可能会导致很多冲突,使用命名空间的目的是对标识符的名称进行本地化,以避免命名冲突或名字污染,namespace关键字的出现就是针对这种问题的
命名冲突
1.我们定义自己的变量,函数可能跟库函数里面重名
2.进入公司项目组以后,做的项目通常比较大。多人协作,两个同事写的代码,命名冲突
3.C语言没有办法很好的解决这个问题
C++提出了一个新语法叫命名空间
//这段代码就是正确的
#include
int rand = 0;
int main()
{
printf("%d", rand);
return 0;
}
//但是当我们加了#include的时候,这段代码就会报错,原因是stdlib.h这个库函数里面,包含了rand函数,在编译的时候,编译器不知道rand到底是以变量的形式存在的还是以函数的形式存在的,命名冲突了
#include
#include
int rand = 0;
int main()
{
printf("%d", rand);
return 0;
}
所谓命名空间就是自己开辟了一块空间,跟全局域和局部域互不干扰,用的时候再拿出来
解决办法(命名空间定义在全局域)
#include
#include
namespace bai
//开辟出来了一个叫bai的空间域
//rand存在这个域里面,要它的时候再用
{
//rand是全局变量,放在静态区的,不能被修改
int rand = 0;
rand=10;//不能赋值
}
int main()
{
printf("%d", bai::rand);
//:: 是域作用限定符,这个意思就是rand取的是bai这个域里面的
//全局域表示--printf("%d", ::rand);
//这个打印出来的是函数的地址
return 0;
}
下面代码是错的,供参考
#include
namespace bai
//开辟出来了一个叫bai的空间域
//rand存在这个域里面,要它的时候再用
{
int rand = 0;
}
int main()
{
printf("%d", rand);
//全局域相当于地球,bai相当于无敌小盒子(怎么都不会被影响),把rand放在小盒子里了,地球上发生的其他事情就跟小盒子里面的东西没关系了,想要小盒子里面的东西的时候打开它就行了
//rand先在局部变量找自己,没有,然后再在全局变量找自己,也没有(如果全局和局部同时有,优先用局部的)
//最后会报错
return 0;
}
namespace bai
{
int rand = 8;
int Mul(int x, int y)
{
return x * y;
}
struct S
{
int a;
};
}
int main()
{
bai::rand = 8;
bai::Mul(1, 2);
struct bai::S s;
}
namespace bai
{
int bai = 8;
int Mul(int x, int y)
{
return x * y;
}
namespace Bai
{
int Bai = 18;
int Add(int x, int y)
{
return x + y;
}
}
}
int main()
{
bai::bai = 8;
bai::Mul(1, 2);
bai::Bai::Bai = 18;
bai::Bai::Add(1, 2);
}
同一个工程中允许存在多个相同名称的命名空间,编译器最后会合并成同一个命名空间中
比如:一个工程Test.h和Test.cpp中的两个名叫M1的命名空间会被合并成一个
命名空间里面可以只放定义, 不放声明
1.加命名空间名称及作用域限定符,指定作用域–做到了最好的隔离,但是使用不方便
namespace bai
{
int a = 10;
}
int main()
{
printf("%d", bai::a);
}
2.用using将命名空间中某个成员引入,用于展开命名空间中常用的
namespace bai
{
int a = 10;
struct b
{
int c;
int d;
};
}
using bai::a;
int main()
{
printf("%d", a);
}
3.使用using namespace 命名空间名称 引入–全部展开,用起来方便了,隔离失效了(慎用)
namespace bai
{
int a = 10;
struct b
{
int c;
int d;
};
}
using namespace bai;
int main()
{
printf("%d", a);
}
#include
//C++的库函数,相当于C语言#include
using namespace std;
//C++的库函数都包含在一个名叫std的域里面,将这个域展开之后才可以用C++的东西
cout--流插入运算符
cin--流提取运算符
int main()
{
cout << "hello" << endl;
//cout是输出流,相当于printf
//endl是换行,相当于‘\n’
return 0;
}
#include
using namespace std;
int main()
{
cout << "hello" << endl;
int i = 10;
double d = 1.11;
//自动识别类型,不需要像printf那样指定类型输出了
cout << i << " " << d << endl;
return 0;
}
#include
using namespace std;
int main()
{
cout << "hello" << endl;
int i = 10;
double d = 1.11;
//cin相当于scanf
cin >> i >> d;
//自动识别类型
cout << i << " " << d << endl;
const char* str = "hello world";
cout << str << endl;
return 0;
return 0;
}
std命名空间使用惯例:
std是c++标准库的命名空间,如何展开std使用更合理呢?
1.在日常练习中,建议直接using namespace std即可,这样就很方便
2.using namespace std展开,标准库就全部暴露出来了,如果我们定义跟库重名的类型/对象/函数,就存在冲突问题。那么这个问题在日常练习中很少出现,但是在项目代码较多,规模大的项目开发中就很容易出现。所以建议在项目开发中使用像std::cout这样类型的命名空间展开方式
通俗一点的来说就是给函数里面的参数赋值
看例子
void fun(int a = 0)//我们平时的函数的参数,a不被赋值,缺省就相当于给a设置了一个默认值
{
cout << a << endl;
}
int main()
{
fun();//没有传参时候,使用参数的默认值
fun(10);//传参时,使用指定的参数值
return 0;
}
void fun(int a = 10, int b = 20, int c = 30)
{
cout << "a=" << a << " ";
cout << "b=" << b << " ";
cout << "c=" << c << " " << endl;
}
int main()
{
fun();//打印出来 10 20 30
fun(1);//传参是从左往右,只有一个,就给a传参,打印出来是1 20 30
fun(1, 2);//打印出来是 1 2 30
fun(1, 2, 3);//打印出来是1 2 3
return 0;
}
void fun(int a , int b = 20, int c = 30)//缺省两个
{
cout << "a=" << a << " ";
cout << "b=" << b << " ";
cout << "c=" << c << " " << endl;
}
void fun(int a , int b , int c = 30)//缺省一个
{
cout << "a=" << a << " ";
cout << "b=" << b << " ";
cout << "c=" << c << " " << endl;
}
以下是错误示范
//示范1
void fun(int a , int b = 20, int c )
{
cout << "a=" << a << " ";
cout << "b=" << b << " ";
cout << "c=" << c << " " << endl;
}
//示范2
void fun(int a=10 , int b , int c = 30)
{
cout << "a=" << a << " ";
cout << "b=" << b << " ";
cout << "c=" << c << " " << endl;
}
可以在声明中出现或者在定义中出现,就是不能同时出现
c++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数或类型或顺序)不同,常用来处理实现功能类似数据不同的问题
int add(int a, int b)
{
return a + b;
}
double add(double a, double b)
{
return a + b;
}
int main()
{
add(1,2);
add(1.1,2.2);
}
void A()
{
cout << "A()" << endl;
}
void A(int a)
{
cout << "A(int a)" << endl;
}
int main()
{
A();
A(1);
return 0;
}
void A(int a,double b)
{
cout << "A(int a,double b)" << endl;
}
void A(double a,int b)
{
cout << "A(double a,int b)" << endl;
}
int main()
{
A(1,2.2);
A(2.2,1);
return 0;
}
注意:
1.如果类型,顺序,个数都相同,但是返回值不同,不能构成重载,因为调用的时候不能区分
2.缺省值不同也不能构成重载
3.void f()与void f(int a=0)构成重载,但是调用存在歧义
4.在C/C++中,一个程序要运行起来,需要经历:预处理(预编译)-编译-汇编-链接
引用不是新定义一个变量,而是给已存在的变量取了一个别名,编译器不会给引用变量开辟内存,它和它引用的变量共同用一块内存空间
比如妻子,你也可以叫媳妇,老婆,都指的同一个人
#include
using namespace std;
int main()
{
int a = 1;
int& b = a;//给a取了个别名叫b,这里的&不是取地址
int* c = &b;//这个&才是取地址
a = 2;//这时候a和b都是2,他们都代表同一个东西,a改了,b也就改了
b = 3;//相同道理,a和b都是3
return 0;
}
1.引用在定义时必须初始化
int a = 1;
int& b = a;//不能直接int& b;
2.一个变量可以有多个引用
int a = 1;
int& b = a;
int& c = a;
int& d = c;
//归根到底都是给1这个玩意取名字
3.引用一旦引用一个实体,再不能引用其他实体
int a = 1;
int& b = a;
int c = 2;
b = c;//这里是把c赋值给b,而不是让b变成c的别名
首先是c语言指针版本传参
void swap(int* px, int* py)
{
int tmp = *px;
*px = *py;
*py = tmp;
}
int main()
{
int x = 10;
int y = 20;
swap(&x, &y);
return 0;
}
下面是引用传参
void swap(int& a, int& b)//a是x的别名,b是y的别名
//交换ab就相当于交换xy
{
int tmp = a;
a = b;
b = tmp;
}
int main()
{
int x = 10;
int y = 20;
swap(x, y);
return 0;
}
先拿普通函数来说,这时候返回的c,并不是直接返回c,而是返回的时候,把c的值放在了一个临时空间中,返回的是这个临时空间的值,而出了add函数之后,add函数的栈帧就被销毁了,c也就跟着销毁了,只能返回了c的临时拷贝的值
int add(int x, int y)
{
int c = x + y;
return c;
}
int main()
{
int a = 1;
int b = 2;
int ret=add(a, b);
return 0;
}
再看传引用返回(这段代码错误,供参考对比)
//传引用返回的意思就是返回了c的别名
不会生成c的拷贝返回,直接返回了c的别名
int& add(int x, int y)
{
int c = x + y;
return c;
}
int main()
{
int a = 1;
int b = 2;
int ret=add(a, b);
return 0;
}
//当前代码的问题
//1.存在非法访问,因为add(1,2)的返回值是c的引用,所以add
//的栈帧销毁了以后,回去访问c的位置
//2.如果add函数栈帧销毁,清理空间,那么取c值的时候取到的就是随机值,
//给ret的就是随机值,当然这个取决于编译器实现
//vs下没有清理
如果函数返回时,出了函数作用域之后,如果返回对象还在(还没还给系统),则可以使用引用返回
int& a()
{
static int a = 0;
return a;
}
如果已经还给系统了,则必须使用传值返回
int add(int x, int y)
{
int c = x + y;
return c;
}
传引用在数据很多且出了作用域不被销毁时,效率相对于传值提高
int main()
{
//权限放大--不可以
const int a = 10;
int& b = a;
//权限不变--可以
const int c = 10;
const int& d = c;
//权限缩小--可以
int e = 10;
const int& f = e;
return 0;
}
假设x是一个大对象或者后面学习深拷贝的对象
//那么尽量用引用传参,减少拷贝,如果f函数中不改变x
//建议尽量用const引用传参
void f(const int& x)//如果没有const,传a传不过来,但是c可以传过来
{
cout << x << endl;
}
int main()
{
const int a = 10;
int c = 20;
f(a);
f(c);
return 0;
}
#include
using namespace std;
int main()
{
double d = 1.1;
int a1 = d;//对--d赋值给a1不是直接复制的,是有一个临时变量把d的值拷贝给a
int& a2 = d;//不对
const int& a2 = d;//对--相同道理,会产生一个临时变量,a3是临时变量的别名
//临时变量是一个右值,不能被改变,要加上const修饰
return 0;
}
右值----通常不能被修改,表达式产生的临时变量,常量就是右值
a=10;
//10就是右值
const+类型+&----可以接收各种类型的对象
使用引用传参,如果函数中不改变参数的值,建议用const&
调用函数,需要建立栈帧,栈帧中要保存一些寄存器,结束后又恢复
//可以看到这些都是有消耗的
//对于类似以下代码频繁调用的小函数,能否优化一下
int add(int a, int b)
{
return a + b;
}
int main()
{
int a = 1;
int b = 2;
add(a, b);
add(a, b);
add(a, b);
add(a, b);
add(a, b);
add(a, b);
add(a, b);
add(a, b);
return 0;
}
为此,有了内联函数
有了内联函数,就不需要c语言的宏,因为宏很复杂,很容易出错
inline int add(int a, int b)
{
return a + b;//相当于直接在main函数的add函数那里展开了,不需要再调用了
}
int main()
{
int a = 1;
int b = 2;
add(a, b);
return 0;
}
特性:
1.内联(inline)是一种以空间换时间的做法,省去调用函数额外开销,所以代码很长(10行以上算长)或者有递归的函数不适宜用作内联函数
2.inline对于编译器只是一个建议,编译器会自动优化,如果定义为inline的函数很长或者递归函数等等,编译器优化时会忽略掉内联
3.inline不建议声明和定义分离,分离会导致链接错误,因为inline被展开,就没有函数地址了,链接就会找不到
图片对应特征3
结论:短小的,频繁调用的函数建议使用内联函数(inline)
int main()
{
//自动推导变量类型
auto a = 10;
auto b = 'a';
auto c = 1.11;
//typeid----打印变量类型
cout << typeid(a).name() << endl;
cout << typeid(b).name() << endl;
cout << typeid(c).name() << endl;
//auto会自动省略const属性
//比如
const int a=1;
auto b=a;
这个时候auto自动识别就是int,而不是const int
return 0;
}
int x = 10;
auto a = &x;//a的类型是int*
auto* b = &x;//b的类型是int*
auto& c = x;//c的类型是int,是x的别名
void fun(auto a)//错的
{
}
int main()
{
int b = 10;
auto a;//auto不能独立定义
//应该改成auto a=b;
//auto也不能作为函数参数
fun(b);
//auto也不能定义数组
auto c[]={1,2,3};//错的
return 0;
}
int main()
{
int arr[] = { 1,2,3,4,5 };
//自动依次取数组arr中的每个元素赋值给a(a只是变量名字,取别的名字也可以)
for (auto a : arr)
{
cout << a << endl;
}
for (int a : arr)//这样也可以,但是auto更爽
{
cout << a << endl;
}
//把数组中每个值加1
for (auto& a : arr)
{
cout << a << endl;
}
return 0;
}
在这里插入代码片void Print(int a[])
{
for (auto x : a)//范围for这么用就不行,范围for里面必须是数组名,这里的a是指针变量
{
cout << x << endl;
}
}
int main()
{
int arr[] = { 1,2,3 };
Print(arr);
return 0;
}
void fun(int)
{
cout << "int" << endl;
}
void fun(int*)
{
cout << "int*" << endl;
}
int main()
{
int* p1 = NULL;
int* p2 = 0;
fun(0);//0是int类型,NULL是int*的指针类型,这两句我们预期是第一句打印int
//第二句打印int*
//可是最后都打印出来了int
fun(NULL);
return 0;
}
原因就是在C++98中,NULL是一个宏,在后台被定义成了0,就是int类型,NULL类型不够清楚,所以C++11中定义了一个新的指针nullptr
它的类型就是int*
void fun(int)
{
cout << "int" << endl;
}
void fun(int*)
{
cout << "int*" << endl;
}
int main()
{
int* p = nullptr;
fun(nullptr);
return 0;
}