第1章、C++基础
1.1 命名空间
1.1.1 命名空间的含义
在使用C++进行大规模程序设计时,开发过程往往是团队合作,各个程序员命名和各种C++库,对标识符的命名可能发生冲突,从而引进命名空间(一种特殊的域)的概念,以避免命名冲突或名字污染。
【例 1-1】命名冲突
// test1.cpp
#include <iostream>
using namespace std;
int a = 1;
int main()
{
cout << a << endl;
return 0;
}
// test2.cpp
int a = 2;
这个程序有test1.cpp和test2.cpp组成,两个文件中同时定义了全局变量 a ,引发命名冲突,故而 a 重定义;
1.1.2 命名空间的定义
C++中,命名空间使用namespace关键字声明,是一种特殊的域。
namespace 命名空间标识符名
{
申明成员;// 可以是变量、函数、类、对象、结构体等
}
【例1-2】命名空间的定义
namespace Gredot1
{
int a;
namespace Gredot2
{
int b;
}
}
从这个例子,我们可以看出,命名空间可以嵌套定义,即命名空间嵌套命名空间。在调用时,需要一步一步指定命名空间域。如果命名空间中找不到对应变量名,将会报错。
// 调用变量 b
Gredot1 :: Gredot2 :: b = 10;
这里" : : ",是作用域限定符,用来指定标识符的作用域。
1.1.3命名空间的使用
- 直接指定标识符命名空间
Gredot1 :: a;
- 使用using namespace命令
using namespace Gredot1;
...
{
a = 10;
}
这种方式是将命名空间完全展开,违背了命名空间设计的初衷,展开有风险,在大型项目中尽量避免使用这种方式。
- 使用using 关键字
using Gredot :: a;
...
{
a = 10;
}
注:同名命名空间
// test1.cpp
#include <iostream>
namespace Gredot1
{
int a;
}
// test2.cpp
namespace Gredot1
{
int b;
}
同一个工程允许存在多个相同名称的命名空间,编译器最后合并成一个命名空间(集合思想)
命名空间的展开和头文件展开的区别:
命名空间展开是指程序编译是是否会到命名空间搜索标识符
头文件展开会将头文件中的内容拷贝到程序代码中
1.2.C++的输入和输出
#include<iostream>
// std是C++标准库的命名空间名,C++将标准库的定义实现都放到这个命名空间中
using namespace std;
int main()
{
cout<<"Hello world!!!"<<endl;
return 0;
}
说明:
- cout和cin是全局的流对象,endl是特殊的C++符号,表示换行输出,他们都包含在包含< iostream >头文件中。
- <<是流插入运算符,>>是流提取运算符。
为了兼容C语言C++的输入输出会对C语言的缓冲区进行同步(检查C语言输入缓冲区),所以cin/cout速度会慢于scanf/printf。
1.3 缺省参数
缺省参数是声明或定义函数时为函数的参数指定一个缺省值。在调用该函数时,如果没有指定实参则采用该形参的缺省值,否则使用指定的实参。
缺省值只能是常量或全局变量。
1、全缺省参数
void func(int a = 5, int b = 6, int c = 9)
{
cout << a<< b << c << endl;
}
int main()
{
func(1,2,3);//a接收1,b接收2,c接收3
func(1,2);//a接收1,b接收2,c使用缺省值9
fun();//a使用缺省值5,b使用缺省值6,c使用缺省值9
return 0;
}
全缺省参数在调用时,不给参数时,将会使用缺省值调用。调用时,实参和形参从左到右匹配。
2、半缺省参数
void func(int a , int b = 6, int c = 9)
{
cout << a << b << c << endl;
}
int main()
{
func(1, 2, 3);//a接收1,b接收2,c接收3
func(1);//a接收1,b使用缺省值6,c使用缺省值9
func();//错误写法,因为形参a不是缺省,至少传一个参数
return 0;
}
未使用缺省值的形参,在传参时必须传入,有缺省值的形参,根据需要可传可不传。
1、缺省必须从右往左缺省,不能a变量给了缺省值,b没给,c给了,这种跳跃的缺省语法是不被允许的。
2、缺省参数不能在函数声明和定义中同时出现,在声明中给了缺省值,定义中就不要给缺省值了!
1.4 函数重载
函数重载是函数的一种特殊情况,C++允许在同一作用域中声明多个同名函数,这些同名函数参数类型、参数个数、类型的顺序不同,常用来处理实现功能类似数据类型不同的问题。
1.4.1 什么情况可以重载
- 函数参数个数不同 && 函数名相同
- 函数参数类型不同 && 函数名相同
- 函数参数顺序不同 && 函数名相同
1.4.2 函数重载原理
命名修饰规则(Linux演示)
在linux下,采用g++编译完成后,函数名字的修饰发生改变,编译器将函数参数类型信息添加到修改后的名字中,所以就能根据形参,来区分同名函数。g++编译器函数名修饰规则【_Z+函数长度 +函数名+类型首字母】
问:为不能用函数返回值作为重载的标志?
答:因为就算函数名修饰规则中有函数返回类型的区分,我们在调用函数传参的时候,编译器是没办法知道我们想要调用哪个函数,会产生二义性。
1.5 引用
引用,就是已定义变量的一个别名。可以给变量取别名,也可以给变量的别名取别名,编译器不会为引用额外开辟空间,他们都代表着同一块空间。取的别名的类型必须与原变量保持一致。
1.5.1 引用特性
- 引用在定义的时候必须初始化
int a = 10;
int&ra; // 未初始化的引用,错误写法
- 一个变量可以有多个引用
int a = 10;
int&b = a;
int&c = b;
// b c都是a的别名
// 将相当于外号
- C++ 引用不能改变指向
int a = 10;
int&b = a;
int x = 20;
b = x; // 赋值操作,而非改变引用指向
1.5.2 常引用(指针和引用的赋值,权限可以缩小不能扩大)
- 对常量的引用
int main()
{
const int& a = 10; // 用常引用作常量的别名
return 0;
}
10是常量,有“常属性”,可读不可写,如果不加const修饰,那么a的权限就变成了可读可写,这是权限的放大,语法是不通过的,所以这里的a必须加上const修饰。(右往左)
- 对const常量的引用
int main()
{
const int a = 10;
const int& ra = a;// 权限平移
return 0;
}
- 隐式、强制类型转换的引用
int main()
{
double a = 10.256;
const int& ra = a;// 权限平移 ra打印结果是10
return 0;
}
变量a发生隐式类型转换,a将自身的数值截断后复制到临时变量中,ra对临时变量进行引用。临时变量具有常属性。所以这里要加const修饰。
1.5.3传参时的权限问题
- 权限只可以平移或缩小,不能放大。【右 - > 左】
2、缺省引用,必须使用const修饰
3、const提升生命周期
值作为返回值时,由于临时变量出作用域销毁,此时会产生临时变量返回,引用作为函数返回值时,不会产生临时变量;
fun函数的返回值使用临时变量带回,ret作为临时变量的引用,加const会延长临时变量的生命周期;
3、引用的使用场景
1.5.4 引用做参数
void Swap(int& a, int& b)
{
int tmp=a;
a = b;
b = a;
}
引用作为函数的形参,函数将直接操作a,b变量本身,而不会产生拷贝
引用做参数的优势
-
减少拷贝,提高效率
-
作为输出型参数,在函数中,形参改变会改变实参。
-
一般引用做参数的(函数中形参不需要改变)都要用const引用
1.5.5引用做返回值
错误用法:
int& Add(int a, int b)
{
int sum = a + b;
return sum; // sum 为局部变量
}
int main()
{
int ret = Add(1, 2);
cout << ret << endl;
return 0;
}
当Add函数调用完毕后,函数栈帧被销毁,同时通过临时变量将sum的别名返回,但是sum它已经被销毁了。所以这是一种错误的用法。
正确用法:
int& Add(int a, int b)
{
static int sum = a + b;
return sum; // sum 为全局变量
}
int main()
{
int ret = Add(1, 2);
cout << ret << endl;
return 0;
}
总结:函数栈帧结束后,如果返回值不会随着栈帧的结束而销毁,那么就可以使用引用返回。
引用返回相较于传值返回的优势
1、减少拷贝,提高运行效率
2、可以直接对返回值进行修改
1.5.6 引用和指针的区别
1、语法上引用是变量的别名,不开空间,而指针存储一个变量地址。但是引用的底层是用指针实现的。
2、引用在定义时必须初始化,指针没有要求
3、引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体 。这就导致了C++的引用代替了一部分指针的功能,但不能完全代替。例如链表中的next必须用指针实现。如果采用引用的方式引用节点,插入删除节点要修改next,没办法通过引用改变next指向的值。
4、没有NULL引用,但有NULL指针
5、在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32
位平台下占4个字节)
6、引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
7、有多级指针,但是没有多级引用
8、访问实体方式不同,指针需要显式解引用,引用编译器自己处理
9、引用比指针使用起来相对更安全