1. 命名空间 namespace
1.1 概念
在程序编写时存在着大量的变量、函数、类等等,如果将这些内容放在全局作用域,难免会出现很多冲突。所以在C++中引入了命名空间这个概念。
在命名空间中对标识符进行名称本地化,避免标识符名称冲突。
1.2 命名空间的定义
在命名空间中,可以定义函数,变量等等。
1.定义普通的命名空间
namespace A
{
int a = 10;
int b = 20;
int sum(int a, int b)
{
return a + b;
}
}
2.嵌套定义命名空间
namespce B
{
int c = 30;
namespace C
{
int a = 10;
struct STU
{
int id;
char name[16];
}
}
}
3.定义同名命名空间
同名的命名空间,编译器会将两个命名空间合成为一个空间,前提是两个空间中不能存在同名的变量或函数。
namespace A
{
int c = 30;
int max(int a, int b)
{
return a > b ? a : b;
}
}
1.3 命名空间的使用
1.常规的使用方式 通过 ::域运算符访问
int main()
{
cout << A::a << " " << A::b << endl;
cout << A::sum(1, 2);
// 使用嵌套命名空间
cout << B::C::a << endl;
return 0;
}
观察发现如果多次使用这个作用域中的变量,每次都要这么写,很麻烦,所以引入了第二种写法。
2.使用using将命名空间中的成员引入
using A::a;
int main()
{
cout << a << B::c << endl;
int d = a;
int f = B::C;
return 0;
}
如果要大量使用当前命名空间中的元素,上面那种方法还是麻烦,所以直接引入命名空间。
3.将命名空间直接引入
using namespace A;
int main()
{
cout << a << endl;
cout << b << endl;
cout << sum(1, 2) << endl;
return 0;
}
2. C++中强大的输入与输出
在C语言中,我们使用的输入输出是printf()和scanf(),在使用时,一不小心,就会错误的使用,不仅要注意类型、注意取地址符有没有加,很麻烦。
所以在C++中,引入了新的输入输出 cout 和 cin。
在使用输入输出时,必须包含 #include <iostream> 这个头文件,以及标准的命名空间 using namespace std;
2.1 标准输入输出的使用
#include <iostream>
using namespace std;
int main()
{
int a;
double b;
char c;
cin >> a;
cin >> b >> c;
cout << a << b << endl;
cout << c << " " << endl;
return 0;
}
3. 方便的缺省参数
3.1 概念
什么时缺省参数,当我们在声明或者定义函数时,给函数的参数指定一个默认值,这就是缺省参数。调用函数时,如果没有对函数中传递实参,则该函数就会默认使用缺省参数。
3.2 分类
1.全缺省参数 (全部参数都有默认值)
int add(int a = 1, int b = 2, int c = 3)
{
return a + b + c;
}
2.半缺省参数 (只有部分参数有默认值,但必须从右向左依次给出)
void displyFun(int a, int b = 1, int c = 2)
{
cout << a << endl;
cout << b << endl;
cout << c << endl;
}
注意缺省参数不能在函数声明和定义中同时出现,如果在声明和定义中同时给出缺省值,编译器则不知道对哪个值进行处理。
4. 函数重载
4.1 概念
什么时函数重载,在我们的中文中,有时会出现一个词有多种意思的情况。那么函数重载意义相同。
在同一个作用域中,函数的名字相同,参数列表不同。这样的函数叫做函数重载。
参数列表不同分为:参数的个数不同,参数的类型不同,参数类型的顺序不同。注意:函数返回值不同不构成重载
4.2 举例
以下函数就构成了函数的重载。
int add(int x, int y)
{
return x + y;
}
double add(double x, double y)
{
return x + y;
}
long add(long x, long y)
{
return x + y;
}
int main()
{
add(1, 2);
add(1.0, 2.0)
add(2, 3);
}
4.3 函数重载的调用原理
在编译阶段,编译器会对函数的类型进行实参推演,根据推演的结果选择合适的函数调用,如果有则进行调用,如果没有合适的函数,编译器会先对类型进行隐式转换,如果转换后有合适类型的函数则调用,如果没有则会报错。
4.4 缺省函数有函数重载吗?
void Test(int a = 10)
{
cout << TestDefault() << endl;
}
void Test(int a)
{
cout << Test() << endl;
}
以上两个函数,虽然满足了函数定义,但在调用时产生了二义性,所以并不会发生函数重载。如果不传递参数,则系统就无法判定应该调用哪个函数。
5. 引用(重点)
5.1 概念
C语言的传参方式:值传递 和 址传递
址传递:在使用时有些麻烦,并且可能会因为空指针的问题出现异常
值传递:虽然安全且使用方便,但在函数中无法通过形参改变实参
C++中为了解决上述问题,引入了引用这一概念。
引用不是去定义一个变量,而是对已经存在的变量起了一个别名,引用与共用一块内存空间。并且可以通过引用值直接修改实体。
5.2 如何使用
语法格式 类型& 引用变量名 = 引用实体
int main()
{
int a = 10;
int& ra = a; // 定义引用类型
ra = 20; // 对 a 的值也进行了修改
return 0;
}
5.3 特性
在定义时,必须要进行初始化,不初始化就会报错引用类型必须与引用的实体类型完全一致一个变量可以有多个引用,但一个引用只能引用一个实体实体的声明周期长于引用不能对常量进行引用 int& ra = 100; // err const int& ra = 100 // ok 注意这种方式,不是和常量使用一块地址空间,而是新开辟了一块空间存储100
5.4 常引用
对引用加上const修饰符就是常引用,常引用则不能通过引用去修改实体。
void testFun()
{
double d = 12.34;
const int& rd = d; // ok
// 这个操作竟然成功了!!!
// rd是谁的引用?
d = 32.34;
cout << rd << endl; // 值没有变化
}
上述程序中,我们需要明白rd到底是谁的引用。修改实体后,引用值没有变化,所以rd不是d的引用。在引用时,double和int进行了隐式转换生成了一个临时变量,rd引用的是该临时变量,但是不知道该临时变量的名字,也不知道该临时变量的空间地址,所以该临时变量具有常性。
5.5 使用场景
5.5.1 变量别名
简化代码,对名字较长的变量取别名,方便使用
5.5.2 做函数参数
做为函数参数,相当于地址传递,不仅安全而且使用方便。
void Swap(int& a, int& b)
{
int tmp = a;
a = b;
b = tmp;
}
int main()
{
int a = 10;
int b = 20;
Swap(a, b);
cout << a << b << endl;
return 0;
}
5.5.3 做函数的返回值使用
注意:不要这样使用
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) = " << ret << endl; // 打印结果为 7
return 0;
}
上述代码打印结果为 7。按照我们的认知,打印结果应该为3,但为什么为7。
因为函数在调用时会建立栈帧,调用完后会将栈帧销毁,但是这个函数的返回值是个引用,返回值会保存栈环境中的值,所以第一次保存了1+2的结果。紧接着又调用了Add(3, 4)。有在相同的位置建立了栈帧,并且引用使用的地址还是该地址,所以又保存了3+4的值。
当引用作为函数返回值时,不要返回函数栈上的空间,可以返回不受函数销毁而影响的值:全局变量、静态变量、引用参数
5.5.4 C/C++几种传值的效率对比
直接看结论,效率对比 传值 < 传址 = 传引用
1.传值:每次传值操作,都相当于将参数值拷贝依次,如果是一个结构体成员并且结构体成员中数据量较大时,则会将参数中的数据一个一个拷贝,效率会很低2.传址:传递*地址,速度快3.传引用:同样传地址速度快
传引用与传地址效率相同,当时引用简化了写法,并且使用更简单。
5.5.5 指针和引用的区别
相同点
传递效率基本相同在传参时都可以到达同样的效果,修改形参改变外部实参在底层的实现方式相同。引用在语法概念上就是一个别名,没有独立空间和实体共用一块内存空间,在底层的实现方式和指针相同。(即引用在底层实际上也有空间,该空间中实际放置的时其所引用的实体)
不同点
引用在定义时必须初始化,指针没有要求引用在初始化阶段引用一个实体就不能在引用其它实体,指针可以随意改变指向。因为 int& -> int* const没有NULL引用,但是又NULL指针在sizeof中的含义不同,sizeof(引用) = 实体类型大小,sizeof(指针) = 地址空间大小。引用自增、实体也自增,指针自增则向后移动一位有多级指针,但没有多级引用访问实体的方式不同引用使用比指针安全,指针可能为空,但引用不能。
6. 内联函数
6.1 概念
内联函数实际上就是C语言中的宏函数,以inline修饰的函数叫做内联函数,编译时编译器会在调用内联函数的地方展开,省去了函数压栈是的开销,以此来提升效率。
inline int sum(int a, int b)
{
return a + b;
}
6.2 特性
以空间换取时间,省去函数额外的调用开销,所以当代码很长或者函数中含有循环递归的函数不适合作为内联函数inline对于编译器只是一种建议,编译器会自动优化,如果内敛函数体中有循环递归等操作西,编译器会忽略内联。inline不建议将声明与定义分离,分离后会导致链接错误,因为inline函数被展开后,就不会有函数地址,而是整块替换,链接时就找不到该函数。
6.3 宏函数和内联函数的区别
宏函数本身没有类型,参数也没有类型,在使用时不会进行类型检查。但内联函数就是函数依旧可以进行调试,与参数的检查。宏函数展开后无法进行代码调试。内联函数可以。
7. auto关键字
7.1 概念
C语言中的auto的关键字含义为,使用auto修饰的变量,是具有自动存储器的局部变量。
C++中的auto关键字,不再是一个存储类型的指示符,而是作为一个新的类型指示符,根据初始化值,自动确定该变量的类型。在编译器编译时推导而得。
void type()
{
auto a = 10;
auto b = 10.1;
auto c = c;
cout << typeid(a).name() << endl;
cout << typeid(b).name() << endl;
cout << typeid(c).name() << endl;
}
7.2 使用方法
7.2.1 auto和指针/引用 联合使用
用auto声明指针类型是,用auto和auto*没有任何区别,但用auto声明引用类型是必须加&
void TestAuto()
{
int a = 10;
auto ra = &a; //相当与对a取地址
auto* b = &a;
auto& c = a; // 引用a
cout << typeid(a).name() << endl;
cout << typeid(ra).name() << endl;
cout << typeid(b).name() << endl;
cout << typeid(c).name() << endl;
}
7.2.2 在同一行声明多个变量
在同一行多个变量时,这些变量类型必须相同,否则编译器会报错,因为编译器只会对第一个类型进行推导,然后根据推导结果定义其他变量。
7.3 auto关键字不能使用的场景
1.auto不能作为函数的参数,因为编译时无法确定参数类型2.auto不能用来声明数组
8. 范围for循环
8.1 概念
对于一个有范围的集合而言,由程序员来定义范围是多余的,所以C++引入了基于范围的for循环。
for(迭代变量 : 迭代范围)
8.2 使用
void TestFor()
{
int arr[] = { 1, 2, 3, 4, 5, 6 };
for (auto& e : arr)
{
e *= 2;
}
for (auto e : arr)
{
cout << e << " ";
}
}
总结
以上的全部内容就是C++相对于C语言中的一些新特性了,因为这篇文章是面向入门的,所以内容没有那么深入。
免责声明:本站所有内容及图片均采集来源于网络,并无商业使用,如若侵权请联系删除。
上一篇:C 中级程序员教程 全目录 v2
下一篇:82页《现代C 教程》:高速上手C 11/14/17/20