为了避免命名冲突和污染,c++提出了命名空间的概念,一个命名空间就定义了一个新的作用域,命名空间中的所有内容都局限于该命名空间中,其用法为:
//定义了一个名为date的命名空间
namespace date
{
//这里面正常定义变量,函数等成员即可
int year=0;
int month=0;
int day=0;
int fun1()
{
//...
}
}
当我们想要使用某个已经定义好的变量或函数时,编译器默认是不会到命名空间中去寻找的,若我们想要用命名空间中的成员时,有三种方法:
①成员前加上命名空间名称及作用限定符:
int i=date::year;
②使用using将命名空间的某个成员引入:
using date::year;
int i=year;
③使用using namespace将命名空间引入:
using namespace date;
int i=year;
int j=month;
int k=date::day;
第②和③种方法本质都是将命名空间里面的成员暴露出来,使其暴露在using位置开始直至所在作用域结束的空间内(注意不是直接将命名空间里面的成员拿出来成为该作用域的成员)。
int main()
{
using namespace date;
int year=0;
int i=year;//执行到这里year产生歧义。出错
}
int main()
{
using namespace date;
int year=0;
//该代码不会报错
}
//命名空间里面的year虽然暴露出来了,但并没有报重定义错误
如果将命名空间里面的成员全部暴露出来,命名空间就失去了存在的意义,因此我们并不推荐将命名空间展开的方法。
最后还有2点:
1.如果在多个或一个文件里名字相同的命名空间会被合并。
2.std是c++标准库的命名空间,c++标准库的定义实现都放到了这个命名空间里面。
c++允许我们在声明或定义函数时为函数的参数指定一个缺省值,当我们没有指定实参时会使用缺省值,改缺省值必须是常量或全局变量。
缺省值只能在函数声明的时候给。
int Sum(int a=1,int b=2)
{
return a+b;
}
int main()
{
int sum1=Sum();//sum1=3
int sum2=sum(3);//sum2=5
}
对于半缺省参数,要缺省的参数要放到没有缺省值的参数之后,传参时不能跳着传
//错误
void fun1(int num1=1, int num2, int num3=3, int num4=4)
{
//...
}
//正确
void fun1(int num1, int num2=2, int num3=3, int num4=4)
{
//...
}
int main()
{
fun1(1,2,3)//正确传参:num1=1,num2=2,num3=3
fun1(1,,,4)//想跳着传参,错误
}
c++允许函数的名字相同,但参数要不同(参数类型、个数、顺序不同皆可)
int fun(int num1,float num2)
{
//...
}
int fun(float num1,int num2)
{
//...
}
//构成函数重载
c语言在编译链接时,会生成一个函数名与地址一一对应的符号表,因此c语言通过函数名就可以找到函数地址,c++也是类似,只不过c++的符号表不是单纯的函数名与地址一一对应,以linux下的编译器为例(不同编译器处理方法不同,但大体相似),会按以下规则生成符号串:
-z+函数名字符个数+函数名+参数类型首字符
fun(int a,char b)//生成的符号串为:-z3funic
然后用该符号串与函数地址一一对应生成符号表,由于函数参数不同,符号串具有唯一性,寻找函数地址时不会产生歧义,这就允许了函数重载。
c++允许为一个已经存在的变量取别名,编译器不会为引用变量再开辟一块空间,而是与被引用的变量共用一块空间。
int mian()
{
int a=1;
int &b=a;//引用
b++;
cout<<"a="<<a;//a=2;
}
引用特性
①引用在定义时必须初始化
②一个变量可以有多个引用
③一旦引用一个实体,引用的指向不在改变
int main()
{
int a=1;
int b=2;
int&c=a;
c=b;//不是将c的引用改为b,而是赋值,a=2;
}
对于常引用,我们只需要知道权限只能平移或缩小,不能放大
int main()
{
const int a=10;
const int &b=a;//正确
int &b=a;//错误,a为常量,b为变量,权限放大
int c=10;
const int&d=c;//正确,权限可以缩小
const int& e=10;//正确
//int& e=10;错误
}
在有类型转换时,编译器会生成一个临时常量,再将其赋给左操作数
int main()
{
double a=1.1;
int& b=a;//出错
const int&b=a;//正确
}
1.用于参数
void Swap(int& a,int& b)
{
int temp=a;
a=b;
b=temp;
}
int main()
{
int a=1;
int b=2;
Swap(a,b);//a=2,b=1
}
2.用于返回值
如果函数返回时,出了函数作用域,要返回的对象还在,可以使用引用返回,否则只能使用传值返回。
int& fun()
{
static int a=1;
return a;
}
使用引用返回可以提高函数返回时的效率
1.引用是一个已经存在的变量的别名,指针是存储变量的地址
2.引用在使用时必须初始化,指针没有要求
3.一旦引用一个实体,引用的指向不在改变,指针可以在任何时候改变指向
4.没有NULL引用,但有NULL指针
5.在sizeof中的意义不同:引用的结果是引用变量的大小,指针结果为地址空间的大小(4/8字节)
6.引用自加加是实体加1,指针自加加是指针向后偏移一个类型的大小
7.有多级指针,没有多级引用
8.访问实体时,对于引用编译器自己处理,指针需要显示解引用
9.引用比指针使用起来相对更安全
宏有许多缺陷,使用起来容易出错,关于宏的介绍,c++喜欢用enum、const代替宏常量,用内联函数代替宏函数
如果在函数前加上关键字inline,函数就会被改成内联函数,编译时c++会在调用内联函数的地方将其展开,没有了函数调用建立栈帧的开销,使程序的运行效率得到提升,但由于内联函数是直接将函数展开,这就使得目标文件变大,因此内联函数是一种空间换时间的做法。
并不是加了inline编译器就会将函数作为内联函数,inline对于编译器来说只是一个建议,如果函数较小(只有几行)、频繁调用、不是递归则成为内联函数,否则编译器忽略inline特性。
内联函数不能声明和定义分离,因为内联函数是要展开的,没有生成符号表的必要,所以如果声明定义分离,编译器在链接时就会出错。
当我们在进行赋值操作时,我们需要清楚的知道右操作数的类型并要准确书写出来,但有时候做到这个很麻烦,因此c++提供了auto关键字,具有自动推导右操作数类型的功能。
int a=10;
auto b=a;//b为int型
auto并不是一种类型,只是一个类型声明的占位符,编译时编译器自动将auto替换成推导到的类型。
1.auto定义变量时必须初始化
auto a;//错误
2.在声明指针类型时,auto与auto*没有任何区别
int a=10;
auto b=&a;
auto* c=&a;
//b、c都是int*型
3.声明引用类型时必须加上&
int a=10;
auto& b=a;
4.一行声明多个变量时,这些变量类型必须相同
编译器只对第一个变量的类型进行推导,然后用推导出来的类型定义该行其他变量的类型,因此如果类型不同就会出错。
auto a = 10, b = 1.1;//出错
auto不能作为函数参数,函数返回值,也不能用来定义数组
void fun1(auto a)//错误
{
//...
}
auto fun2()//错误
{
//...
}
auto a[]={1,2,3};//错误
c语言NULL指针的定义为0或无类型((void*)0)的指针,这就会存在一些问题,c++使用nullptr表示指针空值,为无类型(void*)0。
当for循环迭代范围确定时,可以使用以下方法遍历数组
int main()
{
int a[3]={1,2,3};
for (auto& b:a)//将a数组的每个元素乘2
{
b*=2;
}
}
void fun(int a[])//出错,迭代范围不确定
{
for (auto& b:a)//将a数组的每个元素乘2
{
b*=2;
}
}
cout和cin分别是ostream和istream类型的对象,<<是流插入运算符,>>是流提取运算符。