目录
- 1.命名空间
-
-
- 1.1 什么是命名空间
- 1.2 定义
- 1.3 域
- 1.4 命名空间的使用
-
- 2.缺省参数
-
-
- 2.1 什么是缺省参数:
- 2.3 缺省参数是要在声明里的
-
-
- 函数重载
-
-
- 3.1 什么是函数重载:
- 3.2 C++如何支持函数重载的:
-
- 4.引用
-
-
- 4.1 概念:
- 4.2 用法
- 用处
-
- 内联函数
- auto关键字(C11环境下)
1.命名空间
1.1 什么是命名空间
我们知道,在我们写代码过程中可能我们对变量的命名会与C++的库发生重名冲突。命名空间就是C++用来处理标识符和库之间冲突的关键字------namespace。
在如下代码中:
c
#include<stdio.h>
#include <stdlib.h>
int rand = 0;
int main()
{
return 0;
}

我们运行过后会显示rand重定义这样的报错,这是因为stdlib.h这个头文件的库里面有一个rand函数,我们定义整形变量rand,就会和rand函数发生冲突,导致报错。
1.2 定义
命名空间的定义很简单:关键字+命名空间名字+{}。
c
namespace name
{
}
大括号里面可以是很多类型,整形,函数,结构体等等。
c
//一:
namespace name
{
// 命名空间中可以定义变量/函数/类型
int a = 0;
int b = 1;
int Add(int left, int right)
{
return left + right;
}
struct Node
{
struct Node* next;
int val;
};
}
命名空间可以嵌套
c
namespace N1
{
int a;
int b;
int Add(int left, int right)
{
return left + right;
}
namespace N2
{
int c;
int d;
int Sub(int left, int right)
{
return left - right;
}
}
}
但我们在命名空间域里面写很多大量的东西时,也可能发生冲突,所以我们可以在命名空间里面嵌套命名空间来解决这类问题。
同一个工程中允许存在多个相同名称的命名空间,编译器最后会合成同一个命名空间中。
c
//text1.h:
namespace bit
{
int x = 0;
}
//text2.h:
namespace bit
{
int y = 0;
}
//text.cpp:
#include<iostream>
#include"text1.h";
#include"text2.h";
using namespace std;
int main()
{
cout << "hello world" << endl;
cout << bit::x << bit::y << endl;
return 0;
}
1.3 域
我们将命名空间{}范围内叫做命名空间域,我们将int rand=0在命名空间域里面进行定义,这时候代码就不会报错。这跟全局变量和局部变量的使用是相似的------一个在全局域,一个在局部域。
c
#include<stdio.h>
int a = 1;//全局域
namespace name
//命名空间域
{
int a = 2;
}
int main()
{
int a = 0;//局部域
printf("%d",a);
return 0;
}
这里我们定义三个a,运行代码是没有问题的,这里讲讲域的先后顺序:局部域->全局域->命名空间域。当我们不访问局部域的a时,那么printf就默认打印全局域的a。我们也可以通过使用域作用限定符"::"来访问全局域里面的a。
域作用限定符"::"
对"::"左边进行访问,如果左边为空,则默认为全局域
c
printf("%d",::a);//打印全局域的a
我们的命名空间域里面的内容不能够被默认访问,它需要编译器对命名空间域里面的内容进行搜索才可以使用。
c
printf("%d",name::a);//打印命名空间域里的a
1.4 命名空间的使用
我们知道,命名空间是为了解决标识符和库之间的冲突的,那么像C++的库和STL,我们在定义一些变量或者函数时,可能会发生冲突。所以C++就有一个命名空间std:将C++的库和STL等放在std这个命名空间域里面,就能避免发生冲突。
这里说明一下c++的写法:由于C++兼容C,所以可以使用C中大部分内容
c
#include<iostream>
int main()
{
int a=0;
rerturn 0;
}
命名空间的使用有三种方式:
- 加命名空间名称及作用域限定符
c
int main()
{
printf("%d\n", name::a);
return 0;
}
- 使用using将命名空间中某个成员引入
c
using name::b;
- 使用using namespace 命名空间名称 引入
c
using namespace name;
这个引入命名空间的方法建议在日常训练时使用,像做项目需要很多人共同完成,定义的一些标识符可能与其他人的命名空间域定义的内容发生冲突。
包含头文件和引入命名空间的区别: 包含头文件:是在预处理阶段将头文件里面包含的库等展开,使我们可以使用。 引入命名空间(using namespace std):编译器通过搜索来查找std里面的内容(C++库,STL等)
c
#include<iostream>
using namespace std;
int main()
{
cout<<"hello world"<<endl;
return 0;
}
如果我们不包含头文件的话,程序是无法使用cout和endl的。如果不引入std的话,同样也是无法使用cout和endl的,因为这是命名空间域里面的内容。
2.缺省参数
2.1 什么是缺省参数:
缺省参数是声明或定义函数时为函数的参数指定一个缺省值。在调用该函数时,如果没有指定实参则采用该形参的缺省值,否则使用指定的实参。缺省参数也叫做默认参数。
c
#include<iostream>
using namespace std;
void Default(int a = 0)
{
cout << a << endl;
}
int main()
{
//未给入实参:
Default();//则默认a=0;
//给入实参:
Default(10);//a=10;
return 0;
}
2.2 缺省参数分类:
- 全缺省参数:即全部为缺省参数
c
#include<iostream>
using namespace std;
void Default(int a = 10,int b=20,double c=30.12)
{
cout << a << endl;
cout << b << endl;
cout << c << endl<<endl;
}
int main()
{
//未给入实参:
Default();//则默认a=10,b=20,c=30.12;
//给入实参:
Default(100);//a=100;
Default(100,200);//a=100;
Default(100,200,301.2);//a=100,b=200,c=301.2;
return 0;
}
2.半缺省参数:即部分为缺省参数,这时需要为非缺省参数传入实参:
c
#include<iostream>
using namespace std;
void Default(int a ,int b=20,double c=30.12)
{
cout << a << endl;
cout << b << endl;
cout << c << endl<<endl;
}
int main()
{
Default(100);//a=100,b和c采用默认值;
return 0;
}
同时这里我们需要注意:因为我们实参的传入是由左到右的,也就是我们传入100是传入给形参a的,如果我们将缺省参数c变为非缺省参数,程序会报错。因为程序不知道你这个100是给c还是给缺省参数。因此,半缺省变量都是从右往左依次来给出。
c
void Default(int a=100 ,int b=20,int c)
{
cout << a << endl;
cout << b << endl;
cout << c << endl<<endl;
}
int main()
{
Default(100);//程序会报错,因为不知道100传给缺省参数还是形参c
return 0;
}
2.3 缺省参数是要在声明里的
这是为什么呢,首先要了解预处理阶段和编译阶段:
预处理阶段 会将我们的头文件展开,由于函数的声明一般都在头文件里,所以将头文件展开之后就会得到我们函数的声明,我们的缺省参数也在函数的声明里面。
编译阶段是检查我们程序的语法有没有问题,由于头文件已经展开,函数的声明会在编译阶段被检查,这时候如果缺省参数出现问题就会报错。
缺省参数的使用比如初始化队列,我们要开辟空间,开多少不知道,这时候就可以使用缺省参数。
3. 函数重载
3.1 什么是函数重载:
是函数的一种特殊情况,C++允许在同一作用域中 声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数 或 类型 或 类型顺序)不同,常用来处理实现功能类似数据类型不同的问题。
c
#include<iostream>
using namespace std;
void Add(int a)
{
cout << a + a << endl;
}
void Add(int a,int b)
{
cout << a + b << endl;
}
void Add(int a, double b)
{
cout << a + a << endl;
cout << b + b << endl;
}
void Add(double a, int b)
{
cout << a + a << endl;
cout << b + b << endl;
}
int main()
{
Add(1);
Add(1, 2);
Add(1, 1.1);
Add(1.1, 1);
return 0;
}
3.2 C++如何支持函数重载的:
在C/C++中,一个程序要运行起来,需要经历以下几个阶段:预处理、编译、汇编、链接。
c
//ol.h:声明
#include<iostream>
using namespace std;
void Add(int a);
void Add(int a, int b);
//ol.cpp:定义
#include"ol.h"
void Add(int a)
{
cout << a + a << endl;
}
void Add(int a, int b)
{
cout << a + b << endl;
}
//text.cpp:测试
#include<iostream>
using namespace std;
#include"ol.h"
int main()
{
Add(1);
Add(1, 2);
return 0;
}
地址的分配是定义完之后。
预处理 :头文件展开/宏替换/条件编译/去掉注释等等,接着生成ol.i和text.i文件,ol.i里面包含ol.h等文件的展开和ol.cpp文件的拷贝,同理text.i包含ol.h等头文件的展开和text.cpp。
编译 :检查语法,生成汇编代码,生成ol.s和text.s文件。
汇编 :将汇编代码转换成二进制机器码。生成ol.o和text.o文件,里面是符号表,包含函数名和函数地址。
链接 :生成可执行程序:exe/a.out。
对应关系:

当前text.cpp中调用了ol.cpp中定义的Add函数时,编译后链接前,text.o的目标文件中没有Add的函数地址,因为Add是在ol.cpp中定义的,所以Add的地址在b.o中。那么怎么办呢?链接阶段就是专门处理这种问题,链接器看到text.o调用Add,但是没有Add的地址,就会到ol.o的符号表中找Add的地址,然后链接到一起。
那么链接时,面对Add函数,链接接器会使用哪个名字去找呢?这里每个编译器都有自己的函数名修饰规则 。
函数名修饰规则:
由于Windows下vs的修饰规则过于复杂,而Linux下g++的修饰规则简单易懂,下面我们使用了g++演示了这个修饰后的名字
C语言编译器:

C++编译器:

这里可以看到,C++编译器它的函数名字修饰发生改变而C语言是直接使用函数名,这也是为什么C++支持函数重载而C语言不支持。
4.引用
4.1 概念:
引用不是新定义一个变量,而是给已存在变量取了一个别名 ,编译器不会为引用变量开辟内存空 间 ,它和它引用的变量共用同一块内存空间。引用的符号是'&'。它跟C的取地址符一样但是作用不同,只有当&和类型结合时才是引用。
4.2 用法
c
#include<iostream>
using namespace std;
int main()
{
int a = 10;
int& b = a;//a的别名为b
printf("%d\n",a);
printf("%d\n", b);
printf("%p\n", a);
printf("%p\n", b);
return 0;
}

可以看到他们的值和地址是一样的,同样的,修改b的值也会改变a的值。这是否和我们C的指针很像。
注意:引用是无法改变指向的。也就是b只能是a的引用。
引用的使用比如我们需要使用二级指针才能修改,可以改成一级指针加引用的方式。
c
#include<iostream>
using namespace std;
void revise(int*& ps)
{
int* newnode = (int*)malloc(sizeof(int) * 4);
ps = newnode;
printf("newnode:%p\n", newnode);
}
int main()
{
int a = 20;
int* p = &a;
printf("%p\n", p);
revise(p);
printf("%p\n", p);
return 0;
}

可以看到p的地址被修改了而没有借助二级指针。这里int*& ps指的是引用的形参类型是一级指针。
cpp
typedef struct LTNode
{
}*pNode;//这里的pNode表示的是struct LTNode*
特性:
一个变量可以有多个引用
引用在定义时必须初始化
引用一旦引用一个实体,就不能引用其他实体
常引用 :
假设我们有int类型变量a和double类型变量b,我们将b赋值给a。在这个类型转换的过程中,会产生临时变量,临时变量先拷贝b,然后a再拷贝临时变量,这样就实现了我们的类型转换。由于临时变量是常性的,而我们的引用是可以改变它的值,所以会产生权限的报错。这时候就用到了常引用。
cpp
int main()
{
double d = 1.1;
int& c = d;//错误
const int& c = d;//正确
return 0;
}
引用权限的放大,缩小和平移 :
引用过程,权限不可放大,a不可改变,b能改变,权限方法,错误。
cpp
const int a=10;
int& b=a;
权限可以平移,a,b均不可以被改变。
cpp
const int a=10;
const int&b=a;
int c=20;
int& d=c;
权限可以缩小:修改x的值可以改变y的值,但是y不能自己被修改。
cpp
int x=10;
const int& y=x;
x++;
用处
1.做输出型参数 :
输出型参数:形参的改变影响实参的改变。
输入型参数:形参的改变不影响实参的改变。
cpp
void Fun1(int& x)
{
x = 10;
}
int main()
{
int x = 20;
Fun1(x);
cout << x << endl;//结果为10
return 0;
}
2.做返回值
返回的类型有传值返回和引用返回。
传值返回
cpp
int Fun1()
{
static int n = 0;
n++;
return n;
}
int main()
{
int ret = Fun1();
return 0;
}
传值返回首先创建一个临时变量,临时变量拷贝n,最后ret拷贝临时变量。
引用返回:引用返回不会创建临时变量,所以效率要比传值返回高。
cpp
int& Fun2()
{
static int n = 0;
n++;
return n;
}
int main()
{
int ret = Fun2();
int& ret1=Fun2();
return 0;
}
引用返回返回的是别名,这里我们返回的就是n的别名,这个n的别名是什么无需关注。所以ret和ret1拷贝的是n的别名,其实也就是n。
注意:在引用返回时,创建的变量要在函数结束时不能被销毁,否则会出现随机值的情况。例如我们将n换为非静态变量,那么函数销毁后,为变量n开辟的空会被回收,而ret1和n共用一个空间,所以ret1的值可能会为随机值。
内联函数
在我们定义完一个函数后每次调用它时都要创建栈帧,那么我们调用一万次就要创建一万个栈帧,这是相当麻烦的,在C语言中,我们使用宏对函数进行替换,这样就不需要创建栈帧,但是用宏存在出错,可读性差,不能调试等缺点。在C++中,对C进行优化,内联函数就可以完美的解决这些问题。
内联函数:关键字inline + 函数
作用:,编译时C++编译器会在调用内联函数的地方展开,没有函数调用建立栈帧的开销,内联函数提升程序运行的效率。增加inline关键字变成内联函数,在编译期间编译器会用函数体替换函数的调用。
普通函数:
int Add(int x,int y)
{
return (x + y);
}
int main()
{
for (int i = 1; i <= 10000; i++)
cout << Add(i, i + 1) << endl;
return 0;
}

在反汇编下有call指令,说明对函数进行了调用,也就是会建立栈帧。
内联函数:
inline int Add(int x,int y)
{
return (x + y);
}
int main()
{
for (int i = 1; i <= 10000; i++)
cout << Add(i, i + 1) << endl;
return 0;
}

注意: 在debug模式下,需要对编译器进行设置,否则不会展开函数体(因为debug模式下,编译器默认不会对代码进行优化)
内联函数适用于短小且频繁使用的函数
上述我们知道内联函数在编译期间编译器会用函数体替换函数的调用,也就是要展开函数,假如我们有一个Add函数,里面有50行代码,在一个项目里面,有10000个地方要用到这个函数:
在不变成内联的函数的情况下:每次函数调用都是同一个Add函数,我们大概合计有10050行代码
在内联函数的情况下:每次都要展开Add函数,大概合计50*10000行代码。
这最终就会使我们整个项目的文件变大,非常的不实用。
声明和定义不能分离
内联函数由于是将函数展开作为函数调用,不开辟额外空间,因此不生成地址,不会进入符号表,所以声明和定义分离,链接时会找不到。
cpp
//Func.h
#pragma once
#include<iostream>
using namespace std;
inline int Add(int x, int y);
//Func.cpp
inline int Add(int x, int y)
{
return x + y;
}
//main
#include<iostream>
#include"Func.h"
using namespace std;
int main()
{
for (int i = 1; i <= 10000; i++)
cout << Add(i, i + 1) << endl;
return 0;
}

无法解析的外部命令说明发生了链接错误。
在我们实际写程序的过程中,内联函数也不是想用就能用的,它会受到编译器的限制,编译器会从是否是递归函数,代码行数是否超过编译器设定的行数来决定这个函数是否能成为内联函数:
cpp
//递归从1加到10
#include<iostream>
using namespace std;
inline int Add(int x)
{
if (x == 1)
return x;
else
return x + Add(x - 1);
}
int main()
{
cout << Add(10) << endl;
return 0;
}

cpp
#include<iostream>
using namespace std;
inline void Add(int x, int y)
{
cout << x << " " << y << endl;
cout << x << " " << y << endl;
cout << x << " " << y << endl;
cout << x << " " << y << endl;
cout << x << " " << y << endl;
cout << x << " " << y << endl;
cout << x << " " << y << endl;
}
int main()
{
Add(1, 2);
return 0;
}

auto关键字(C11环境下)
auto可以根据右表达式来自动匹配响应的类型,在类型很长的使用中有很好的效果。
cpp
auto a=1+2;//匹配整形
auto b=1.1;//匹配浮点型。
auto常见的一个使用是范围for(语法糖):
cpp
int main()
{
int arr[] = { 1,2,3,4,5 };
for (auto e : arr)
{
cout << e << endl;
}
return 0;
}
这段代码的解释是依次取数组中的数据拷贝给e,这里不可修改数组的值,要是想修改,换成auto&类型。这里最主要的是" : ",auto可以改成其他类型,不过在一些长的类型中,auto是最合适的。