c++关键字
C语言有32个关键字,c++有63个关键字,下图是c++的部分关键字。
命名空间
namespace发明缘由
函数,变量和类的名字都存在全局作用域中,很有可能导致冲突和名字污染。
#include<stdlib>
#include<stdio.h>
int rand=10;
int main()
{
printf("%d",rand);
}
运行之后报错
C语言无法解决这个问题,于是c++就提出命名空间namespace。
命名空间定义
定义命名空间时,在namespace关键字后面跟着的是命名空间的名字,后面再接一对{ },{ }里面就是命名空间的成员(包括函数和变量)。
1.命名空间可以定义函数,变量,类型
#include<iostream>
using namespace std;
namespace solution
{
int rand = 10;
}
int main()
{
cout << solution::rand << endl;
}
2.命名空间可以嵌套
#include<iostream>
using namespace std;
namespace solution
{
int rand = 10;
namespace lyb
{
int Add(int x, int y)
{
return x + y;
}
}
}
int main()
{
cout << solution::rand << endl;
cout << solution::lyb::Add(1, 2) << endl;
}
允许存在相同名字的命名空间,编译器最后会合到一个命名空间中,如果是在不同的文件中的命名空间最后也会合并。
命名空间的使用
声明:由于rand和std里的rand有冲突,接下来都只展示Add函数。
1.加入命名空间名称和域作用限定符
#include<iostream>
using namespace std;
namespace solution
{
int rand = 10;
}
int main()
{
cout << solution::rand << endl;
}
2.用using将命名空间里某个成员引入
#include<iostream>
using namespace std;
namespace solution
{
int rand = 10;
namespace lyb
{
int Add(int x, int y)
{
return x + y;
}
}
}
using solution::lyb::Add;
int main()
{
cout << solution::rand << endl;
cout << Add(1, 2) << endl;
}
使用using namespace引入
#include<iostream>
using namespace std;
namespace solution
{
int rand = 10;
namespace lyb
{
int Add(int x, int y)
{
return x + y;
}
}
}
using namespace solution::lyb;
int main()
{
cout << solution::rand << endl;
cout << Add(1, 2) << endl;
}
输入/输出
1.使用标准输入输出的时候必须包含头文件以及std命名空间。
2.<<是流插入运算符,>>是流提取操作符。
3.无需像printf一样手动控制输出对象类型,会自行识别。
4.cout和cin分别是istream和osteam类型的对象,>>和<<属于运算符重载。
#include<iostream>
using namespace std;
int main()
{
int a, b;
cin >> a >> b;
cout << a << " " << b << endl;
}
缺省参数
缺省参数的概念
缺省参数是声明和定义函数时为函数的参数指定的一个缺省值,在调用函数时,如果没有传参就调用缺省值,若传参则缺省值不用。
#include<iostream>
using namespace std;
void func(int n = 0)
{
cout << n << endl;
}
int main()
{
func();
func(1);
return 0;
}
缺省参数分类
1.全缺省参数
顾名思义,就是所有参数都有缺省值。
void func(int a=0,int b=1,int c=10)
{
cout<<a<<b<<c<<endl;
}
2.半缺省参数
不是所有的参数都缺省。
注意:半缺省参数只能从右往左给,不能间隔着给,也不能在声明和定义中同时出现,缺省值必须为常量或者全局变量,C语言不支持缺省参数。
void func(int a,int b=10,int c=0)
{
cout<<a<<b<<c<<endl;
}
函数重载
概念
c++允许在一个域里面出现功能类似的同名函数,这些同名函数的形参列表不同(参数个数/参数类型/参数类型顺序不同)
1.参数类型不同
#include<iostream>
using namespace std;
int Add(int x, int y)
{
return x + y;
}
double Add(double x, double y)
{
return x + y;
}
int main()
{
Add(1.0,1.0);
Add(1,1);
return 0;
}
2.参数个数不同
#include<iostream>
using namespace std;
int Add(int x, int y)
{
return x + y;
}
int Add(int x, int y,int z)
{
return x + y + z;
}
int main()
{
Add(1,2,3);
Add(1,2);
return 0;
}
3.参数类型顺序不同
#include<iostream>
using namespace std;
int Add(char x, int y)
{
return x + y;
}
int Add(int x, char y)
{
return x + y;
}
int main()
{
Add('a', 1);
Add(1, 'a');
return 0;
}
原理
函数名字的修饰发生改变,编译器将函数参数类型信息添加到修改后的名字中。
在vs下可以转到反汇编。
可以看到两个Add函数后面括号的地址是不一样的,每个编译器都有自己的函数名修饰规则。
注意:如果两个函数函数名和参数是一样的,返回值不同是不构成重载的,因为调用时编译器没办
法区分。
引用
定义
引用相当于给一个变量取了一个别名,新变量和原先变量共用同一个内存空间。
void func()
{
int a = 10;
int& ra = a;
printf("%p\n", &a);
printf("%p\n", &ra);
}
若你运行了上诉代码,你会发现上述代码输出的地址是一模一样的。
引用特性
1.引用必须初始化
2.一个实体可以有多个引用
3.引用一旦引用一个实体,就不能引用其他实体
4.关于常引用,要注意不能权限放大,const类型的变量和常量在这方面是类似的。
void func()
{
const int a = 10;
//int& ra = a; // 该语句编译时会出错,a为常量,是一种权限放大
const int& ra = a;
// int& b = 10; // 该语句编译时会出错,b为常量,也是一种权限放大
const int& b = 10;
double d = 12.34;
//int& rd = d; // 该语句编译时会出错,类型不同
const int& rd = d;
}
应用场景
1.做参数
2.做返回值
int& Add(int a, int b)
{
int c = a + b;
return c;
}
int main()
{
int& ret = Add(1, 2);
//Add(3, 4);//把这个代码屏蔽掉,ret输出的值可能是正确的,但如果没有屏蔽掉,那么ret就是一个随机值
cout << ret <<endl;
return 0;
}
上述代码其实是有问题的,因为已经出了Add函数栈帧,c变量以及c值返回时候产生的临时变量都已经销毁,其实是已经无法访问了,只是在vs下没有报错,但如果你再多运行一个函数,把Add使用过的空间占用了,你会发现输出的ret是个随机值,如上述代码。
传值和传引用的效率比较
若是传值,函数不会直接传递实参或者让变量本身直接返回,而是传递一份临时拷贝,效率其实是低下的,特别是考虑到参数和返回值类型非常大时,效率就很低。
#include <time.h>
struct A{ int a[10000]; };
A a;
// 值返回
A func() { return a;}
// 引用返回
A& func(){ return a;}
void TestReturnByRefOrValue()
{
// 以值作为函数的返回值类型
int begin1 = clock();
for (size_t i = 0; i < 100000; ++i)
func1();
int end1 = clock();
// 以引用作为函数的返回值类型
int begin2 = clock();
for (size_t i = 0; i < 100000; ++i)
func2();
int end2 = clock();
// 计算两个函数运算完成之后的时间
cout << "func1 time:" << end1 - begin1 << endl;
cout << "func2 time:" << end2 - begin2 << endl;
}
我们可以看见这个效率差别是很大的。
引用和指针的区别
引用的底层逻辑其实就是指针,用引用只是为了便于理解。
1.引用必须初始化,而指针没有这个要求。
2.引用不能变换实体,而指针可以随时改变存储另一个同类型的地址。
3.没有空引用,但有空指针。
4.在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节大小。
5.引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小。
6.有多级指针,但是没有多级引用(比如说引用的引用还是同一个实体)。
7.访问实体方式不同,指针需要显式解引用,引用编译器自己处理。
内联函数
作用
编译时C++编译器会在调用内联函数的地方展开,没有函数调用建立栈帧的开销,能够提高程序的效率。
特性
1.编译阶段,会用函数体替换函数调用。
2.使用inline只是对编译器的一个建议,如果选择内联的函数太大,编译器会选择忽略这个指令。
3.用在非递归,规模小,调用频繁的函数上。
4.不能声明和定义分离在不同文件里,因为inline函数已经展开,编译器在链接的时候是找不到地址的,就会链接错误。
auto关键字
产生缘由
1.变量类型复杂
2.容易拼写错误
3.typedef不能解决所有问题
typedef char* pstring;
#include<iostream>
using namespace std;
typedef char* pstring;
int main()
{
const pstring p1;
const pstring* p2;
return 0;
}
因为typedef不是简单的 宏替换,p1报错是因为前面的变量类型不是被替换成const char而是char const。
注:使用auto定义变量时必须对其进行初始化,在编译阶段编译器需要根据初始化表达式来推导auto
的实际类型。因此auto并非是一种“类型”的声明,而是一个类型声明时的“占位符”,编译器在编译时会将auto替换为变量实际的类型。
规则
1.auto与auto是没有区别的,但使用auto时右边必须是一个指针。
int main()
{
int x = 10;
auto a = &x;
auto* b = &x;
auto& c = x;
cout << typeid(a).name() << endl;
cout << typeid(b).name() << endl;
cout << typeid(c).name() << endl;
*a = 20;
*b = 30;
c = 40;
return 0;
}
2.在同一行声明多个变量时,这多个变量必须是同一种类型,否则会报错,因为auto只对第一个变量进行推导,然后用推导出来的类型定义其他变量。
3.auto不能声明函数参数,也不能直接用来声明数组。
范围for
用法:for循环后的括号由冒号“ :”分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围。
int arr={1,2,4,5,7,8};
for(auto x:arr);
使用条件:
1.for循环的范围必须是确定的,对于数组而言,就是数组中第一个元素和最后一个元素的范围;对于类而言,应该提供begin()和end()的方法,begin()和end()就是for循环迭代的范围。
以下代码就有问题
void func(int array[])
{
for(auto& e : array)
cout<< e <<endl;
}
2.迭代对象要实现++和==的操作。
指针空值
在c++中,NULL被定义为0,只有nullptr才是空指针。