1.命名空间
目的:解决c语言的缺陷,命名冲突。
#include<stdio.h>
int rand=0;
int main()
{
printf("%d",rand);
}
上面这段程序是可以运行的
但是!
#include<stdio.h>
#include<stdlib.h>
int rand=0;
int main()
{
printf("%d",rand);
}
那么上面的代码就会报错。因为stdlib.h 那么包含了rand这个函数 ,我们重新定义rand 那么出现报错。命名被重复定义了。
1.1命名空间定义一个域
关键字 namespace +空间名
namespace std
// namespace name
{
string a="zhangsan";
}
1.2访问全局域和命名空间作用域
//全局域
int a=0;
//命名空间
namespace bit
{
int a=1;
}
//局部域
int main()
{
int a=2;
printf("%d\n",::a); //访问的全局域
printf("%d\n",a); //访问局部域2
printf("%d\n",bit::a); //访问命名空间
}
变量搜索顺序:局部域→全局域→展开命名空间or指定访问命名空间域。
展开命名空间 ------→相当于把变量暴露到了全局 ,编译时是否去命名空间搜索
using namespace std; //可以直接使用命名空间的变量不重复
1.3 定义命名空间
命名空间内可以定义函数、变量、结构体、和再嵌套命名空间。
namespace bit
{
// 命名空间中可以定义变量/函数/类型
int a = 0;
int b = 1;
int Add(int left, int right)
{
return left + right;
}
struct Node
{
struct Node* next;
int val;
};
}
int main()
{
// 编译报错:error C2065: “a”: 未声明的标识符
printf("%d\n", a);
return 0;
}
1.4 命名空间的合并
当我们在多个头文件里面定义了同名的命名空间,当我们调用这些头文件的时候会进行合并两个定义的变量,结构体、函数、嵌套的命名空间,切记不要重复定义变量和函数,可以选择再嵌套一个命名空间来包含这个变量。
1.5 注意事项
using namespace std;
直接展开会有风险,我们定义如果和库重名,就报错了。
建议项目不要直接展开,还是推荐直接指定来访问。
那么问题来了,项目里面我都去指定?
解决办法:把常用展开,只是展开部分
using std::cout;
using std::endl;
2.C++输入&输出
<< 叫流插入运算符
叫流提取运算符
cout<<"hello heshiqiang"<<endl;
cin>>x;
关键字 :cout cin
特性:自动识别类型 ,可以不用定义类型
作用:输入输出
注意:cin自动识别精度到小数点后一位。 当我们要输入和输出小数点后2位,那么还是乖乖用printf和scanf来控制输入输出。
3.函数重载
c中还是不能进行函数重载。同一作用域下c++中可以重复定义多个同名的函数,但是有一定的要求,如下:
- 函数名相同,参数类型顺序不同
- 函数名相同,参数个数不同
#include<iostream>
using namespace std;
//第一个add
int add(int a, int b)
{
return a + b;
}
//第二个add
void add(double a, double b)
{
printf("%lf\n", a + b);
}
int main()
{
//函数重载
int ret = add(10, 20);
printf("%d\n", ret);
add(10.0, 20.0);
//这里体现的是函数名相同,参数类型顺序不同
//同理 我们可以改变参数个数 一样可以调用同名函数
return 0;
}
注意: 改变函数的返回值是无法进行函数重载,必须是上面规定的两条。
那么调用速度会不会变慢?
编译之后,代码变成指令,通过指令找到函数的地址,速度还是很快的,那么如何找到呢,我们打开反汇编指令查看可以发现,c++对函数名进行了修饰,,call找到修饰之后的地址,实现这个函数重载功能。
c++调用函数时候add之后进行了修饰,call指令通过修饰后的地址找到这个函数
那么如何修饰的呢?
第一个add: XXaddii(ii,就是int int)
第二个add:XXadddd(dd,就是double double)
这也是为什么顺序不同,找到的函数会不一样
4.引用
4.1 引用概念
引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。
比如:李逵,在家称为"铁牛",江湖上人称"黑旋风"。
相当于给人取别名
&
1.取地址
例如:scanf("%d",&a);
2.引用
例如:int& k=i;
- &放到变量前面,那么就是取地址
- &放到数据类型的后面,那么就是引用
下面就是演示代码
int main()
{
int a = 10;
int& k = a;
int b = a;
cout << &a << endl;
cout << &k << endl;
cout << &b << endl;
return 0;
}
//实现结果
可以看到就是a就是k,k就是i。那么a++了,k也就++了,不影响b。
4.1.1引用特性总结
- 引用在定义时必须初始化
- 一个变量可以有多个引用
- 引用一旦引用一个实体,再不能引用其他实体
4.2 多层引用问题
//重新给a取一个别名 这个是合理的
//我们允许一个变量有多个别名
int& m=a;
//这个也是被允许的 m是a的别名 那么给m再起一个别名
int& j=m; // x
那么我们可以推广到指针的别名,结构体的别名,函数的别名
4.3使用场景
4.3.1做参数(输出型参数)
1.形参的改变要改变实参
2.提高效率(大对象/深拷贝)
void Swap(int& a,int& b)
{
int temp=a;
a=b;
b=temp;
}
#include <time.h>
#include<iostream>
using namespace std;
struct A { int a[10000]; };
void TestFunc1(A a) {}
void TestFunc2(A& a) {}
void TestRefAndValue()
{
A a;
// 以值作为函数参数
size_t begin1 = clock();
for (size_t i = 0; i < 10000; ++i)
TestFunc1(a);
size_t end1 = clock();
// 以引用作为函数参数
size_t begin2 = clock();
for (size_t i = 0; i < 10000; ++i)
TestFunc2(a);
size_t end2 = clock();
// 分别计算两个函数运行结束后的时间
cout << "TestFunc1(A)-time:" << end1 - begin1 << endl;
cout << "TestFunc2(A&)-time:" << end2 - begin2 << endl;
}
int main()
{
TestRefAndValue();
return 0;
}
运行截图:
4.3.2做返回值
- 常用的传值返回(没有用&)
int add(int a,int b)
{
return a+b;//返回的时候如果是小量数据那么就会被放到寄存器里面 开一个临时变量
//来存储这个临时拷贝
}
- 引用返回
注意事项:就是函数的放回,出了作用域,如果返回的对象没有还给系统,那么才能使用引用传值。(如何保持出了定义的函数作用域还不还给系统,那么我们一般定义全局变量)
#include<iostream>
using namespace std;
int c=0;
int& add(int a,int b)
{
int c=a+b;
return c;
}
int main()
{
int a=add(10,10);
}
- 在返回大对象的时候,引用返回和常用的传值返回比较
#include <time.h>
#include<iostream>
using namespace std;
struct A{ int a[10000]; };
A a;
// 值返回
A TestFunc1() { return a;}
// 引用返回
A& TestFunc2(){ return a;}
void TestReturnByRefOrValue()
{
// 以值作为函数的返回值类型
size_t begin1 = clock();
for (size_t i = 0; i < 100000; ++i)
TestFunc1();
size_t end1 = clock();
// 以引用作为函数的返回值类型
size_t begin2 = clock();
for (size_t i = 0; i < 100000; ++i)
TestFunc2();
size_t end2 = clock();
// 计算两个函数运算完成之后的时间
cout << "TestFunc1 time:" << end1 - begin1 << endl;
cout << "TestFunc2 time:" << end2 - begin2 << endl;
}
int main()
{
TestReturnByRefOrValue();
}
运行结果:
通过测试,传值和用引用在效率方面有很大的区别。引用效率更高。
引用返回值的时候权限的改变
int func1()
{
static int x = 0;
return x;
}
int& func2()
{
static int x = 0;
return x;
}
int main()
{
//const int& ret1 = func1(); //权限平移
//int& ret1 = func1(); //权限放大
//int ret1 = func1(); //拷贝
//int& ret2 = func2(); //权限平移
//const int& ret2 = func2(); //权限缩小 被const修饰具有常性不可修改
//出现整型提升 整型转换 就一定会生成一个临时变量来先整型提升或者转换再赋值
double dd=1.11;
int ii=dd;
const int & rii=dd;
//在发生整型提升或者整型转换 都会生成一个临时变量,然后就是把临时变量放到rii里面
// //然后 临时变量具有常性 const修饰之后也有常性
return 0;
}
4.4 引用和指针之间的区别
- 引用是和原来的实体共用一个空间,相当于还是同一个人,只是不同的名称,不需要开辟空间。
- 引用是按照指针的方式实现的。
int main()
{
int a = 10;
//语法层面:不开空间,是对a取别名
int& ra = a;
ra = 20;
//语法层面:开空间,存储a的地址
int* pa = &a;
*pa = 20;
return 0;
}
上面一段代码的汇编代码对比
我们可以通过观察发现,底层实现原理是一致的。
引用和指针的不同点:
- 引用概念上定义一个变量的别名,指针存储一个变量地址。
- 引用在定义时必须初始化,指针没有要求
- 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体
- 没有NULL引用,但有NULL指针
- 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节)
- 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
- 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
- 引用比指针使用起来相对更安全
10.权限不能放大,例如 const int a=0; int& b=a; 这个地方就是错误的
引用总结: 1.、基本任何场景都可以用引用传参。
2、谨慎使用
5.auto关键字特性和使用
我个人认为auto挺好用的,不用自己推断数据类型,编译器自动帮我推断,当写工程的时候,可以避免因为单词拼写错误出现的报错。
5.1定义
auto不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得。
5.2auto的特性
1.可以推导初始化的数据类型
2.要想使用auto必须进行初始化
3.可以同行声明多个变量,但是变量数据类型要一致
5.3auto的使用
#include<iostream>
using namespace std;
int main()
{
auto a=10; //初始化 并能推断出a的类型为int
auto b=10,c=11; //同行定义多个变量 多个变量数据类型是一致的
cout<<a<<" "<<b<<" "<<c<<" "<<endl;
return 0;
}
int main()
{
//int a = 0;
//int b = a;
//auto c = a; //根据右边表达式的类型推导c的类型
//auto d = c + 1.11;
//cout << typeid(c).name() << endl;
//cout << typeid(d).name() << endl;
vector<int> v;
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
//简化使用迭代器
auto a = v.begin();
//遍历数组读出数组内容
for (auto i : arr)
{
cout << i << " " << endl;
}
//修改数组里面的内容
for (auto& i : arr)
{
i *= 2;
}
return 0;
}