其实C++初阶的语法都是在填C语言的坑,C++祖师爷写C语言的时候看到了很多的问题,觉的一门语言难用就写一门比它好用的语言,可太牛鼻了。
1.namespace
1.1简介
解决C语言命名冲突。
1.2用法
用法有点像结构体,可以在namespace里面定义变量,函数,和类型。还可以嵌套使用。使用的时候在前面加上'::' 域作用限定符就可以访问namespace里面的定义的东西,如果在namespace定义一个变量但是你在引用的时候不加域作用限定符,编译器就相当于完全看不到这个变量。
namespace n
{
int a = 0;
int add(int a ,int b)
{
return a+b;
}
struct Node
{
struct Node* next;
int val;
};
namespace x
{
int rand = 2;
}
}
int main()
{
int a = 1 ,b = 2;
printf("%d",n::a);
n::add(1,2);
struct n::node node;
printf("%d"n::x::rand);
return 0;
}
1.3namespace的展开
有时候一个函数或者变量和类型需要频繁使用的时候可以把namespace进行局部展开,或者全部展开(不建议容易出问题)。
像c++的标准库的函数和对象都是在 namespace std 中声明的,这和C语言的区别就出来了,即使包含了头文件,我们也不能直接使用C++标准库,因为声明在namespace std里面,不展开或不使用'::',编译器根本找不到。
useing namespace n;//全局展开
useing n::add;//局部展开
1.4namespace的合并
例如 在test1.h 定义了一个namespace n 又在test2.h中定义了一个namespace n ,在test.c中引用了test1.h和test2.h,这时候两个头文件的namespace n 就合并成了一个。
1.5注意
不建议在namespace中定义函数,因为有多个.c文件去包含namespace所在的头文件的时候,会出现函数的重定义问题,也可以用static取消在namespace中定义函数的外部链接属性,在别的文件中就找不到了,就不会出现重定义的问题。
2.缺省函数
2.1简介
调用函数的时候可以不传参数或者只传部分参数。
2.2用法
void fun(int a = 1)
{
printf("%d",a);
}
int main()
{
fun();
fun(2);
return 0;
}
第一次不传参数调用的结果就是1,第二次传了个2调用的结果就是2。
2.3全缺省和半缺省
全缺省:在定义函数的时候所有的参数都是已经初始化了,不传参数也可以直接调用,像上面的fun就是一个全缺省。
半缺省:在定义函数的时候不是所有的参数都被初始化,调用的时候必须传参数。
void add(int a ,int b = 2)
{
return a+b;
}
2.4注意
半缺省必须从右向左给缺省值,举个例子。
void fun1(int a = 1,int b ,int c =2)
{
return 0;//错误示范1
}
void fun2(int a =1 int b =2 ,int c)
{
return 0;//错误示范2
}
void fun3(int a ,int b =2,int c=3)
{
return 0;//正确示范
}
3.函数重载
3.1简介
在C语言中是不允许有同名的函数的出现的,但是在C++里只要两个同名函数函数构成了重载,那么这两个函数就可以同时存在。
3.2用法
两个同名的函数只要这两个函数的参数个数,参数类型,参数类型的顺序,有一个不同就可以构成函数重载。
void fun(int a,int b)
{
return 0;
}
void fun(double a,double b)
{
return 0;
}
void fun(int a,double b,double c)
{
return 0;
}
void fun(double a,int b,double c)
{
return 0;
}
像我们调用fun(a,b),a和b都是整形的话,调用的就是第一个fun。
如果fun(a,b,c),a是整形,b和c是双精度浮点型,调用的就是第三个函数。
3.3注意
返回值的不同不能构成重载。
例:这两个函数不构成重载。
int add (int a,int b)
{
return a+b;
}
double add(int a, int b)
{
return a+b;
}
4.流插入和流提取
4.1简介
打印和输出
4.2用法
#include<iostream>
useing std::cout;//打印printf
useing std::endl;//换行'\n'
useing std::cin;//输入scanf
int main()
{
int i;
cout<<"helloword"<<endl;
cin>> i >>endl;
cout<< i <<endl;
return 0;
}
这里和C语言有不一样的地方,就是不用输入 打印和输出的类型 ,是不是很神奇,其实本质就是函数的重载,我打印的是整形就调用打印整形的cout,打印浮点型就调用打印浮点型的cout。
5.引用
5.1用途
引用传参,和引用返回
5.2用法
就是在类型和变量名字之间加了一个&,下面这个代码,b就是a的引用,你可以理解为b就是a的别名。
int main()
{
int a = 1;
int & b = a;
return 0;
}
这个就相当于给a起了一个外号b,假如我叫王五,我的舍友给我取了一个外号叫福根,这个时候福根是我,王五也是我。
打印a和b的数值和地址都是相等的也是直接说明了a和b是在同一块空间的,切记b不是a的拷贝,你改a的值,b也会跟着变。
5.3引用的特性
1.引用在定义的时候必须初始化。
2.一个变量可以有多个引用。
3.引用一旦有初始化,就不能再初始化了。
5.4引用传参
1.引用传参可以提高效率
给函数传参的时候,形参是实参的一份临时拷贝,如果我的实参特别大,传过去的时候就需要在函数的栈帧里开辟一大块空间,然后再把实参拷贝过去,非常的不人性。
但是如果我们直接传引用,我就相当把实参不拷贝,也不开辟空间,直接传过去了,大大的节省了时间非常的人性。
2.引用传参形参可以修改实参
例如:我们在写一个交换函数的时需要把,实参的指针传过,通过指针去修改交换的实参,非常的麻烦,这个时候如果我们用引用做参数,即使不传指针,也可以完成交换,非常的人性。
void swap(int &a,int &b)
{
int ret = a;
a = b;
b = ret;
}
int main()
{
int x = 4 , int y = 5;
swap(x,y);
}
5.5传返回引用
1.传返回引用可以提高效率
正常我们函数返回值的时候,这个函数已经结束,函数的栈帧已经销毁,这个时候返回值已经被拷贝到一个具有常属性的临时空间,然后再把这个临时变量返回给main函数。
但是切记我们用传返回引用的时候,返回值不可以是出了这个函数的作用域就销毁的变量,
就像下面这个c肯定就是不行的,函数结束的时候空间都销毁了,如果没建立新的栈帧还能打印出来,建立新的栈帧之后,这块c的空间都不知道是什么东西了,再打印肯定是随机值。
int &Add(int a, int b){
int c= a + b;
return c;
}
int main()
{
int &ret=Add(1, 2);
Add(3, 4);
cout<<"Add(1, 2) is :"<<ret<<endl;
return0;
}
实际的输出结果却是这个,因为c出了作用域被销毁了,然后又调用了Add(1,2)重新建立栈帧把原来c的空间覆盖了。
2.传引用返回可以修改返回对象
正常函数返回是把返回值拷贝回来,无论怎么修改都不会影响,传引用返回就相当把这个空间的钥匙给你了,你想怎么改都可以。
5.6引用和指针的区别
引用和指针在上层来说,引用是和实体共用一块空间,是实体的别名,没有自己的独立空间。
但是在底层来看,引用就是用指针来实现的,所以引用本身是有空间的。
这是引用和指针的汇编代码
lea指令的就是取地址的意思
都是取出地址放到eax里然后 eax把取出的地址放到ra和pa中。
5.7引用的权限问题
引用的权限不能扩大,只能平移或者缩小。
例:
int func()
{
int a = 0;
return a;
}
int main()
{
const int& ret = func();//权限的平移
const int a = 0;
// 权限的放大
int& b = a;//a是加了const的具有了常属性,但是b没有常属性,就相当把a的权限放大了
int b = a; //可以的,因为这里是赋值拷贝,b修改不影响a
// 权限的平移
const int& c = a;
// 权限的缩小
int x = 0;
const int& y = x;
int i = 0;
const double& d = i;
return 0;
}
6.内联函数
6.1简介
这个也是祖师爷为了填C语言宏函数的坑设计出来的,宏函数非常容易出错,可能让祖师爷用的很不爽。
以inline修饰的函数叫做内联函数,在调用时会在调处进行展开,展开简单理解,就是把调用处给替换了,像宏一样,这么做可以不调用函数,就可以减少栈帧的开销。
6.2用法
正常函数调用的时候会有call的指令,很明显这个是替换过去的。
6.3注意
1.inline只是我们给编译器的一个建议,编译器用不用,我们说的不算。
2.inline最好是用在函数比较小并且会经常复用的函数,递归是用不了的。
3.inline的声明和定义必须在一起,不然会出现链接错误,因为inline被展开了,没有这个函数的地址了,就找不到这个函数了。
7.auto
7.1简介
auto 可以推导参数类型。
7.2用法
std::vector<std::string>::iterator 是一个类型的名字,很长很长,这个时候直接一个auto接收,简直不要太香。
std::vector<std::string> v;
std::vector<std::string>::iterator it = v.begin();
auto it = v.begin();
7.3注意
1.auto在声明引用类型的时候必须加& auto & a = x;
2.使用auto同一行定义多个变量时,切记前后的类型要是一样的,因为编译器只会推导第一个类型,然后用推导出来的类型定义其他变量。
错误示范
auto a = 3,b = 1.00;
3.auto 不能做参数
4.auto不能直接声明数组
8.范围for
8.1简介
可以自动帮程序员确定范围
8.2用法
下面这两行代码是一样的 。 大概意思 就是 把数组array的内容取出来放到e 中,自动的迭代,和停止。
for (auto e : array)
{
cout << e << " ";
}
for(int i = 0;i<sizeof(array)/sizeof(int);i++)
{
count << array[i] << " ";
}
8.3注意
数组的范围一定是确定的才可以用。
9.nullptr
9.1简介
就是C++定义的空指针。
void f (int a)
{
cout<<"fun(int a)"<<endl;
}
void f (int* a)
{
cout<<"fun(int*a)"<<endl;
}
int main()
{
f(NULL);
f((int*)NULL);
}
我们的本意是想传个空过去,去调用一下这个指针版本的函数,但是因为NULL的值是0,所以第一调用的就是整形的版本。
所以以后为了代码的强健度建议使用nullptr