文章目录
一、命名空间
1、为什么会有命名空间
在c语言中存在全局域和局部域,当在局部域和全局域都定义一个名称相同的变量时,编译器会优先使用当前局部域的变量,但当在同一域中存在命名相同变量时就会造成命名冲突,在代码很多的情况下,很多的变量、函数以及类都存在全局域中,这样就有可能造成冲突,为了解决这个问题引入了命名空间,命名空间的作用是对标识符的名称进行本地化,以避免命名冲突或名字污染。
2、命名空间的使用
使用命名空间的关键字 :namespace
使用命名空间时使用的作用域限定符 : (命名空间的名称) ::(命名空间中的成员)
A : 基础使用
namespace test
{
int a = 10;
}
int a = 20;
int main()
{
cout << a << endl;//全局变量的 a
cout << ::a << endl; //当 ::符的前面没有指定的域,就是指全局域
cout << test::a << endl;// :;符指定时在test这个域中找成员 a
return 0;
}
B ; 命名空间镶套使用
namespace test
{
int a = 10;
namespace T //镶套
{
int b = 20;
}
}
int main()
{
cout << test::a << endl; //使用命名空间里的a
cout << test::T::b << endl; //当需要使用镶套的命名空间时,需要一级一级的引用
return 0;
}
当然这里只是镶套一个,是可以镶套多个的,多个的话需要一级一级引用。
C ; 命名空间的展开
关键字:using
使用 :using namespace 命名空间名称
作用:使用其成员不用加作用限定符引用。
展开命名空间的全部成员
namespace test
{
int a = 10;
int b = 20;
}
using namespace test;
int main()
{
//不用使用限定符了
cout << a << endl;
cout << b << endl;
return 0;
}
D ; 如果在全局域中又出现了命名相同的变量、类、函数会怎么捏?
答:会报错,出现不明确的情况。
为了解决上述问题
我们可以只展开我们需要的用的成员,和不会和全局域冲突的成员
使用:using 命名空间名称::(需要展开的成员)
namespace test
{
int a = 10;
int b = 10;
}
using test::b; //只展开b
int a = 20;
int main()
{
cout << a << endl; //a没有展开也没有用限定符,是全局变量
cout << b << endl; //展开了,不用使用限定符
return 0;
}
E : 不同的命名空间存在成员名称相同的情况会这么样捏?
答 :也会出现不明确的报错。
面对这种情况来说,也可以像上面那样,只展开部分,避免冲突。
F ; 当存在局部变量名称与展开的命名空间的成员名称相同时的情况会怎么样捏?
答:优先使用局部变量 。
namespace test
{
int a = 10;
int b = 10;
}
using namespace test; //展开test的全部
int main()
{
int a = 20;
cout << a << endl;
return 0;
}
G ; 当出现命名空间的名称一样时,相同名称的命名空间会合并(同一个文件和多个文件都是一样的)
namespace test
{
int a = 10;
}
namespace test
{
int b = 20;
}
//只展开test的全部
using namespace test;
int main()
{
cout << a << endl; //第一个命名空间
cout << b<< endl; //第二个命名空间
return 0;
}
二、缺省参数
1、概念:
缺省参数是声明或定义函数时为函数的参数指定一个缺省值。在调用该函数时,如果没有指定实参则采用该形参的缺省值,否则使用指定的实参。
2、全缺省参数
指的是函数的每个参数都指定一个缺省值。
//全缺省
int add(int x = 10, int y = 10)
{
return x + y;
}
int main()
{
cout << add() << endl;//无缺省参数,都用指定值 20
cout << add(20) << endl;//只传一个实参 30
cout << add(20, 20) << endl;//都传了实参 40
return 0;
}
3、半缺省参数
只的是有些形参没有指定的缺省值,并且一定要从右向左存在缺省值,中间不能空。
//半缺省
int add(int x , int y = 10, int z = 10 )
{
return x + y + z;
}
int main()
{
cout << add(10) << endl;//只传一个 x = 10 y, z 使用缺省值; 30
cout << add(10,20) << endl;//传两个 x = 10 ,y = 20 z只用缺省值 ; 40
cout << add(10, 20,30) << endl;//都传了实参 x = 10 ,y =20 ,z=30 ;50
return 0;
}
中间空的情况:
后面空的情况:
4、缺省参数只能在定义或者声明中存在,不能两个都出现
5、缺省参数的应用
比如我们在初始化一个顺序表时我们不知道要存储多少数据时,我们可以使用缺省参数给它一个初始值,如果知道存储的数据很多时,我们就传一个很大的值去初始化空间,如果知道存储的数据很少时,我们就传一个小一点的值去初始化空间,这样可以尽量避免空间多次扩容和浪费空间的情况。
void initialize(int capacity = 4) //不知道空间多大时,默认为4
{
//用初始化的容量去申请空间
int* a = (int*)malloc(sizeof(int) * capacity);
}
三、函数重载
1、概念
函数重载:是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数 或 类型 或类型顺序)不同,常用来处理实现功能类似数据类型不同的问题。
2、参数类型不同
//参数类型不同
int add(int x, int y)
{
return x + y;
}
double add(double x, double y)
{
return x + y;
}
int main()
{
cout << add(1, 1) << endl; //整形 2
cout << add(1.1, 1.2) << endl; //浮点型 2.3
return 0;
}
3、参数类型顺序不同
void test(int x, char y)
{
//....
cout << "void test(int x, char y)" << endl;
}
void test(char x, int y)
{
//....
cout << "void test(char x, int y)" << endl;
}
int main()
{
//类型不同
test(1, 'a');
test('a', 1);
return 0;
}
4、参数个数不同
//类型个数不同
void test1(int x)
{
cout << "void test1(int a)" << endl;
}
void test1(int x, int y)
{
cout << "void test1(int x, int y)" << endl;
}
int main()
{
//类型个数不同
test1(1);
test1(1, 1);
return 0;
}
5、需要注意的点
a.不用作用域下,相同名称的函数不属于函数重载
namespace test2
{
void t(int x, int y)
{
//....
cout << "void t(int x, int y)" << endl;
}
}
namespace test3
{
void t(char x, char y)
{
//....
cout << "void t(char x, char y)" << endl;
}
}
//展开
using namespace test2;
using namespace test3;
int main()
{
t(1, 1);
t('a', 'a');
return 0;
}
b.返回类型不能作为重载的条件
c.缺省参数不能作为重载的条件
6、C语言为什么不支持重载而C++支持捏?
结论:C语言在链接时是通过函数名去找函数的,而C++是通过函数名修饰之后再去找函数的。
四、引用
1、概念
引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空 间,它和它引用的变量共用同一块内存空间。如 小张是张三的别名 ,小张和张三指的是同一个人
2、基础使用
引用操作符: &
使用 :(类型) & = (需要取别名的变量)
注意:
a. 定义时必须初始化,不能定义完了先放在那里。
b. 一旦引用了一个实体就不能引用其他的实体了。
c. 一个变量可以被多次引用。
d.引用类型必须和引用实体是同种类型的。
int main()
{
int a = 10;
//错误例子
//定义时未初始化
/*int& b;
b = a;*/
//正确的使用 :定义时初始化
//给a取别名 ,c是a的别名
int& c = a;
//多次引用
int& d = a;
int& e = d;
//引用类型和实体类型不一致,会报错
/*double f = 1.12;
int& e = f;*/
cout << "a: " << a << endl;
cout << "a的地址:" << &a<<endl;
cout << "c: " << c << endl;
cout << "c的地址:" << &c << endl;
cout << "d: " << e << endl;
cout << "d的地址:" << &d << endl;
cout << "e: " << e << endl;
cout << "e的地址:" << &e << endl;
int g = 20;
//这是一个赋值,而不是重新引用
d = g;
//改变d ,其他别名也会跟着改变(改变一个别名或者实体其他也会跟着改变)
cout << "改变d后" << endl;
cout << "a: " << a << endl;
cout << "c: " << c << endl;
cout << "d: " << d << endl;
cout << "e: " << e << endl;
return 0;
}
3、引用的使用场景
a. 做函数参数
通常函数传参数都是使用传值或者传址的,传值调用的形参是实参的一份拷贝形参的改变不会影响实参,传址调用是通过指针再去调用实参,而引用的形参是实参的一个别名,用的是同一块空间。
//引用做函数参数
//x是a的别名 , y时b的别名
void Swap(int& x, int& y)
{
cout << "x: " <<x<< endl;
cout << "x的地址: " << &x<<endl;
cout << "y: " << y<<endl;
cout << "y的地址: " << &y<< endl;
int tmp = x;
x = y;
y = tmp;
cout << "改变后的x: " << x << endl;
cout << "改变后的y: " << y << endl;
}
int main()
{
int a = 10;
int b = 20;
cout << "a: " <<a<< endl;
cout << "a的地址: " << &a << endl;
cout << "b: " <<b<< endl;
cout << "b的地址: " << &b << endl;
Swap(a, b);
cout << "改变后的a: " << a << endl;
cout << "改变后的b: " << b << endl;
return 0;
}
b. 作为函数的返回值
int & test()
{
//...
//动态申请
int* a = new int;
*a = 10;
cout << a << endl;
return *a;
}
int main()
{
//通过引用接收
int &b = test();
cout << &b << endl;
return 0;
}
注意:
用引用作为函数的返回值时,需要注意的是通过引用返回的变量是其本身,所以返回的变量不能是出了函数就销毁的,需要是全局或者是申请的空间的变量才行。
c, 传值、引用的效率比较
以值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直接返回,而是传递实参或者返回变量的一份临时的拷贝,因此用值作为参数或者返回值类型,效率是非常低下的,尤其是当参数或者返回值类型非常大时,效率就更低。
传值和引用效率对比
//检测时间函数的头文件
#include <time.h>
struct Node
{
int a[10000];
}N;
//传值
void test1(Node p)
{
//...
}
//引用
void test2(Node & p)
{
//...
}
void test3()
{
Node p;
size_t begin1 = clock(); //开始
for (int i = 0; i < 100000; i++)
{
test1(p);
}
size_t end1 = clock(); //结束
size_t begin2 = clock(); //开始
for (int i = 0; i < 100000; i++)
{
test2(p);
}
size_t end2 = clock(); //结束
cout << "传值" << end1 - begin1 << endl;
cout << "引用" << end2 - begin2 << endl;
}
int main()
{
test3();
}
运行结果:
做返回值比较
//返回的值
Node a;
//传值
Node test4()
{
return a;
}
//引用
Node& test5()
{
return a;
}
void test6()
{
size_t begin1 = clock(); //开始
for (int i = 0; i < 100000; i++)
{
test4();
}
size_t end1 = clock(); //结束
size_t begin2 = clock(); //开始
for (int i = 0; i < 100000; i++)
{
test5();
}
size_t end2 = clock(); //结束
cout << "传值" << end1 - begin1 << endl;
cout << "引用" << end2 - begin2 << endl;
}
int main()
{
test6();
}
运行结果:
4、 权限问题
权限放大不行,权限放小则可以。
int main()
{
const int a = 10; //修饰为常量 a只读
// int& b = a; //b可读可写 权限放大 ,错误
const int& b = a; //正确
int c = 10; //c可读可写
const int & d = c; //d只读 ,权限放小 , 正确
// int& e = 10; //e可读可写 , 10为常量 ,只读 权限放大
const int& e = 10; //正确
double f = 1.1;
int g = f; //会发生隐式转换 g = 1 ,正确
//隐式类型转换以及表达式计算 在赋值给变量前就已经生成了一个临时变量 ,用这个临时变量在赋值
//如 f赋给g ,会先生成const int tmp = 1 ,再用 g = tmp
// int& h = f;//h可读可写 ,f发生隐式转换,const int tmp = 1 //tmp 只读 ,权限放大
const int& h = f; //正确
int i = 10, j = 10;
// int& k = i + j; //与上面一样 错误
const int& k = i + j;
return 0;
}
5、引用与指针的区别
在语法概念上引用就是一个别名,没有独立空间,和其引用实体共用同一块空间。
int main()
{
int a = 10;
int& b = a;
cout << "a:" << sizeof(a) << endl;
cout << "b:" << sizeof(b) << endl;
return 0;
}
在底层实现上实际是有空间的,因为引用是按照指针方式来实现的。
int main()
{
int a = 10;
int& b = a;
int* c = &a;
return 0;
}
通过反汇编观察 ,在底层引用的实现和指针一样的
引用和指针的不同点:
- 引用概念上定义一个变量的别名,指针存储一个变量地址。
- 引用在定义时必须初始化,指针没有要求。
- 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何 一个同类型实体。
- 没有NULL引用,但有NULL指针。
- 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节)。
- 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小。
- 有多级指针,但是没有多级引用。
- 访问实体方式不同,指针需要显式解引用,引用编译器自己处理。
- 引用比指针使用起来相对更安全。
五、内联函数
1、概念
以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数调用建立栈帧的开销,内联函数提升程序运行的效率。
2、关键字
inline 函数定义
inline int Add(int x, int y)
{
return x + y;
}
3、特点
a. 内联函数是一种用空间换时间的做法,在编译阶段将函数展开,缺点:目标文件变大,优点:提高效率
b.inline对于编译器而言只是一个建议,不同编译器关于inline实现机制可能不同,一般建议:将函数规模较小(即函数不是很长,具体没有准确的说法,取决于编译器内部实现)、不是递归、且频繁调用的函数采用inline修饰,否则编译器会忽略inline特性。
c. 不能定义和声明分离,因为成为内联函数后就不存在地址,这样再通过声明去找地址时就会出现找不到的情况,出现链接错误。
4、宏的优缺点
优点: 提高复用性。
提高效率。
缺点:
可读性差、可维护性差
不方便调试。
没有类型检查C++有哪些技术替代宏?
- 常量定义 换用const enum
- 短小函数定义 换用内联函数
六、auto关键字(C++11)
1、作用:
当类型难于拼写时来代替该类型,由编译器根据初始值推导
2、使用:
int main()
{
auto a = 10;
//int a = 10;
auto b = 'a';
//char b = 'a'
//typeid求类型名称
cout << typeid(a).name() << endl;
cout << typeid(b).name() << endl;
return 0;
}
注意:
使用auto定义变量时必须对其进行初始化,在编译阶段编译器需要根据初始化表达来推导auto的实际类型。因此auto并非是一种“类型”的声明,而是一个类型声明时的“位符”,编译器在编译期会将auto替换为变量实际的类型。
实现了一些 typedef 没解决的问题
如:
typedef char* pstring;
int main()
{
const pstring p1; //const 修饰 p1 , 所以必须要初始化 ,err
const pstring* p2; //const 修饰 *p2 , 所以不必初始化
return 0;
}
使用auto 自动推导就不会出现上述情况
3、细节问题
a.当用来推导指针时 auto与auto * 的含义是一样的,但是引用时一定要加 &。
int main()
{
int a = 10;
auto* p1 = &a;
auto p2 = &a;
auto& p3 = a;
//typeid求类型名称
cout << typeid(p1).name() << endl;
cout << typeid(p2).name() << endl;
cout << typeid(p3).name() << endl;
return 0;
}
b.当一行定义多个变量时,类型一定要一致
int main()
{
//相同类型
auto a = 10, b = 20;
//不同类型
auto c = 10, d = 1.1;
return 0;
}
4、不能推导的情况
a. 作为函数参数
void test(auto a) //err
{
//...
}
b. 作为数组推导
auto a[100] = { 0 }; //err
c. 为了避免与C++98中的auto发生混淆,C++11只保留了auto作为类型指示符的用法
d. auto在实际中最常见的优势用法就是跟以后会讲到的C++11提供的新式for循环,还有lambda表达式等进行配合使用。
七、基于范围的for循环(C++11)
1、作用 :遍历数组,自行实现 ++ 操作 ,并自动结束。
2、使用:
int main()
{
//数组
int arr[5] = { 1,2,3,4,5 };
// 遍历过程: 将arr的元素赋值给 i ,再进行 ++ 再赋值
for (auto i : arr) //语法结构
{
cout << i << " ";
}
cout << endl;
//当我们明确类型也可以这样使用
for (int i : arr)
{
cout << i << " ";
}
return 0;
}
3、改变数组元素
我们知道遍历数组只是将值赋给 i
,i
是一个临时变量,改变 i
不会影响到数组元素的值,所以用想要改变数组元素的值时,可以使用引用,arto &
int main()
{
//数组
int arr[5] = { 1,2,3,4,5 };
//修改
for (auto &i : arr)
{
i *= 2;
}
for (auto i : arr)
{
cout << i << " ";
}
return 0;
}
八、 指针空值nullptr(C++11)
在C语言中使用 NULL ,代表空指针,而NULL实际上是 一个宏, 值为0 ,但是有一些场景里就会歧义。
//整形
void test(int a)
{
cout << "void test(int a)" << endl;
}
//指针
void test(int* a)
{
cout << "void test(int *a)" << endl;
}
int main()
{
test(NULL);
test(NULL);
return 0;
}
使用nullptr
注意:
- 在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为新关键字引入的。
- 在C++11中,sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同。
- 为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr。