目录
目录
一.命名空间
在C/C++中,我们有时候忘记已经被收入到头文件的函数名和关键字,却在运用时都把它们放在全局作用域里,可能就会导致很多冲突。为了避免命名冲突或名字污染,namespace关键字的出现就是针对这种问题。
例如以下代码。
#include<stdio.h>
#include<stdlib.h>
int rand=10;
int main()
{
printf("%d\n",rand);
return 0;
}
//该代码会编译错误:error C2365:"rand":重定义;
在学习C语言的时候,我们知道rand函数是包含在头文件#include<stddlib.h>中,该代码中的变量rand和函数rand重名了。为了解决这种问题,C++提出了关键字namespace。
1.namespace的定义
要使上面的代码运行成功就要使用到namespace关键字。
#include<stdio.h>
#include<stdlib.h>
namespace MY
{
int rand=10;
}
int main()
{
printf("%d\n",MY::rand);
return 0;
}
由上面的代码可以看出
(1.namespace的用法:namespace+命名空间的名字,然后再接一对{}即可,{}中即为命名空间中的成员。命名空间中可以定义变量/函数/类型等。
(2.namespace本质是定义出另一个域,这个域跟全局变量各自独立,不同的域可以定义同名变量,所以上面代码中rand不冲突了。
(3.C++中的域有函数局部域,全局域,空间命名域,类域;域影响的是编译时语法的查找一个变量/函数/类型出处(声明或定义)的逻辑。在C语言中我们学习到的局部域和全局域还会影响变量的生命周期,但是值得注意的是命名域和类域不影响变量的生命周期。
(4.namespace只能定义在全局,但可以嵌套调用。
#include<stdio.h>
#include<stdlib.h>
namespace MY
{
int rand=10;
namespace new1
{
int rand=30;
}
}
int main()
{
printf("%p\n",rand);//这里默认访问的是全局的rand函数指针
printf("%d\n",MY::rand);//这里指定new命名空间中的rand
//输出10
printf("%d\n",MY::new1::rand);//这里指的是new命名空间中new1命名空间中的rand
//输出30
}
(5.项目工程中多文件中定义的同名namespace会认为是一个namespace,不会冲突。
例如,在项目工程中Stack.c和Test.c文件中都定义了一个namespace new的命名空间,相当于在
把两个文件中namespace new中的成员和并在一起。
(6.C++标准库都放在一个叫std的命名空间中。这个命名空间中包括cout(输出)、cin(输入)等。
2.命名空间的使用
编译查找一个变量声明/定义时,默认只会在局部和全局查找,不会再命名空间中查找,所以上面虽然在命名空间中定义了rand变量,但是编译器查找不到,程序就会报错。所以我们要使用命名空间中定义的变量/函数,有三种方式:
(1.指定命名空间访问。上面代码中的MY::rand。
(2.using将命名空间中某个成员展开,项目中经常访问的且不存在冲突的成员推荐。
所以可以将上面的代码改成
#include<stdio.h>
namespace MY
{
int ret = 10;
int rand = 20;
}
using MY::rand;//展开命名空间MY的成员rand
//using namespace MY;//展开命名空间的全部成员
int main()
{
printf("%d\n", rand);//如果存在头文件#include<stdlib.h>时即使展开了rand也会报错
//因为编译器不知道rand是哪个rand
printf("%d\n", MY::ret);
return 0;
}
(3.展开命名空间中全部成员,项目不推荐,冲突风险很大。
二.C++输入和输出
1.<iostream>是标准的输入、输出流库。vs中该库也包含printf、scanf,其他编译器可能报错。
2.std::cout是ostream类的对象,它主要面向窄字符的标准输出流;std::cin是istream类的对象,它主要也是面向窄字符的标准输入流。
3.std::endl是一个函数,流插入输出时,相当于插入了一个换行符加刷新缓冲区。
4.<<是流插入运算符,>>是流提取运算符。
以上这四点的运用如下图
#include<iostream>
using namespace std;
int main()
{
int a;
cin >> a;//输入23
cout << a << endl;//输出23
}

5. C++的输入输出可以自动识别变量类型(本质是通过函数重载实现的)。
6.cout/cin/endl等都属于C++标准库,C++标准库都放在一个叫std的命名空间中。如上面的代码中using namespace std;一般训练的时候可以这么写,但实际项目开发中不建议。
三.缺省参数
之前在学习C语言的时候发现函数形参的变量如果在调用在这个函数的时候没有写实参,都会报错。而缺省参数就是在声明和定义和函数时为函数的参数指定一个缺省值。当我们在调用该函数的时候,即使没有写实参,也会采用该形参的缺省值,如果指定了实参,则应用实参,缺省参数分为全缺省和半缺省参数。
1.全缺省就是所有形参都指定缺省值,半缺省就是部分形参给缺省值(不是一半给一半不给)但半缺省参数必须从右往左依次连续缺省,不能间隔跳跃给缺省值。
2.调用带缺省函数时,C++会从左往右依次给实参,不能跳跃给实参。
3.函数声明和定义分离时,缺省参数不能在函数声明和定义中同时出现,规定必须函数声明给缺省
值。
#include<iostream>
using namespace std;
void func(int a = 10, int b = 20, int c = 30)
{
cout << "a=" << a << " ";
cout << "b=" << b << " ";
cout << "c=" << c << endl;
}
void func1(int a, int b = 30, int c = 40)
{
cout << "a=" << a << " ";
cout << "b=" << b << " ";
cout << "c=" << c << endl;
}
int main()
{
func();
func(30,40, 50);
func1(10);
func1(10, 50);
}
结果如下:

三.函数重载
C++支持在同一作用域中出现的同名函数,但是要求这些同名函数的形参不同,可以是参数个数不同或者类型不同。而C语言是不支持同一作用域存在同名函数的。
1.可以重载的函数只要满足其中一个都可以。
(1)参数类型不同。如int Add(int x,int y)和double Add(double x,double y)构成重载。
(2)参数个数不同。如void f()和void f(int a)构成重载。
(3)参数类型顺序不同。如void f(int a,char b)和void f (char b,int a)构成重载。
但注意函数的返回值不能作为重载的条件,因为调用时无法区分。
上面我们学习了缺省参数,那么void f()和void f(int a=10)构成重载吗?它们的参数个数不同当然是构成重载的 ,但是当函数调用方式为f()时,会存在歧义,编译器不知道要调用哪个。
四.引用
1.引用的定义和概念
引用其实就是给变量取别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一个内存空间。定义:类型&引用别名=引用对象。
int main()
{
int a = 10;
int& ra = a;
int& rb = ra;
cout << &a << endl;
cout << &rb << endl;
cout << &ra << endl;
a++;
cout << "a=" << a << " ";
cout << "b=" << rb << " ";
cout << "c=" << ra << endl;
}
运行结果如下:

在上面的结果中我们可以看到变量a、ra、rb它们的地址是相同的,这就表明它们三个都指向同一块空间,所以当a的值变化时,ra、rb的值也变化了。
2.引用的特性
(1.引用在定义时必须初始化。
(2.一个变量可以有多个引用。
(3.引用一旦引用了一个实体,在不能引用其他实体。
int main()
{
int a = 10;
//int& ba;//编译错误:ba必须初始化
int& ba = a;
int c = 20;
//int& ba = c;//编译错误:ba重定义;多次初始化(根据第三点)
cout << "a=" << a << " ";
cout << "b=" << ba << " ";
cout << "c=" << c<< endl;
}
3.引用的使用
(1.引用实践中主要是引用传参和引用做返回值中减少拷贝提升效率和改变引用对象的同时改变被引用对象。如在函数传参的时候,在形参中引用,即是实参的别名。和指针传参的功能类似。
(2.指针和引用在实践中相辅相成,功能有重叠性,但各有特点,互相不可替代。但是C++引用定义后不能改变指向。如不能像指针一样在链表中指向下一个地址等。
void swap1(int* x, int* y)
{
int tmp = *x;
*x = *y;
*y = tmp;
}
void swap2(int& x, int& y)
{
int tmp = x;
x = y;
y = tmp;
}
int main()
{
int a = 10, b = 30;
swap1(&a, &b);
cout << a << " " << b << endl;
int c = 20, d = 40;
swap2(c, d);
cout << c << " " << d << endl;
}
但是如果把函数的返回值直接用来运算能不能运行成功呢?如以下代码。
int f(int* a,int n)
{
return a[n - 1];
}
int main()
{
int a[] = { 1,3,4,5 };
int n = sizeof(a) / sizeof(a[0]);
f(a, n) += 10;
cout << f(a, n) << endl;
return 0;
}
答案是不可以的,为什么呢?
在学习c语言的时候我们知道在函数运行结束后编译器会自动销毁空间,所以我们返回的只是它的值,并没有它那块地方的地址。而如果把函数改成int &f(int *a,int n)就可以表示为给a数组中的n-1的下标的元素取别名,所以在f(a,n)中用的是那个元素的别名。

插个题外话。
如果把定义一个变量,把a[n-1]的值给变量,程序还能运行成功吗?
int& f(int* a,int n)
{
int ret = a[n - 1];
return ret;
}
int main()
{
int a[] = { 1,3,4,5 };
int n = sizeof(a) / sizeof(a[0]);
f(a, n) += 10;
int tmp = f(a, n);
cout << tmp << endl;
}
答案是可以成功,但是它并没有改变f(a,n)的值。

上面我们提到了变量出了函数就会销毁,而上面直接返回函数的是因为原本那个数组就存在并不会销毁,而那个变量出函数运用时编译器就找不到它的空间了。
二级指针也可用引用代替。例如链表中的。
typedef struct ListNode
{
int val;
struct ListNode* next;
}LTNode, *PNode;
// 指针变量也可以取别名,这⾥LTNode*& phead就是给指针变量取别名
// 这样就不需要⽤⼆级指针了,相对⽽⾔简化了程序
//void ListPushBack(LTNode** phead, int x)
//void ListPushBack(LTNode*& phead, int x)
void ListPushBack(PNode& phead, int x)
{
PNode newnode = (PNode)malloc(sizeof(LTNode));
newnode->val = x;
newnode->next = NULL;
if (phead == NULL)
{
phead = newnode;
}
else
{
//...
}
}
4.引用中const引用
在C语言中我们学过const的用法——用来定义常量,如果一个变量被const修饰,那么它的值就不能再被改变。
(1.可以引用一个const对象,但是必须用const引用。const引用也可以引用普通变量,因为对象的访问权限在引用过程中可以缩小,但不可以放大。
(2.不需要注意的是类似 int& rb = a*3; double d = 12.34; int& rd = d; 这样⼀些场
景下a*3的和结果保存在⼀个临时对象中, int& rd = d 也是类似,在类型转换中会产⽣临时对
象存储中间值,也就是时,rb和rd引⽤的都是临时对象,⽽C++规定临时对象具有常性,所以这⾥
就触发了权限放⼤,必须要⽤常引⽤才可以。所谓临时对象就是编译器需要⼀个空间暂存表达式的求值结果时临时创建的⼀个未命名的对象,C++中把这个未命名对象叫做临时对象。
int main()
{
const int a = 10;
//int &ra=a;//编译错误,权限变大了。
const int& ra = a;
int b = 10;
const int& rb = b;//这里的引用是对b权限的缩小。
int c = 10;
const int& rc = c * 3;//c*3是临时变量,具有常性,触发权限变大。
}
五.指针与引用的异
(1.语法概念上,引用不用开空间,而指针需要开空间存储变量的地址。
(2.引用在定义时必须初始化,指针建议。
(3.引用在初始化时引用一个对象后不能再引用别的对象,而指针可以不停的改变对象。
(4.引用可以直接访问指向对象,指针需要解引用才能访问对象。
(5.sizeof中含义不同,引用的结果就是该变量的类型大小,比如int a=4;int &ra=a;sizeof(ra)=4。但指针是个地址随着机器地址的不同而改变(32位平台下占4个字节,64位平台下占8个字节)。
(6.指针很容易出现空指针和野指针的问题,引用很少出现。
六.inline(内联函数)
在学习C语言的时候,学到了宏函数,它在预处理时会替换展开。Add的宏函数是#define Add(x,y) ((x)+(y))。括号是不是很麻烦,而且不方便。所以C++设计了内联函数替代C的宏函数。
(1.编译时C++编译器会在调⽤的地⽅展开内联函数,这样调⽤内联函数就不需要建⽴栈帧了,就可以提⾼效率。
(2.inline适用于频繁调用的短小函数,对于递归函数,代码相对较多的函数。加上inline也会被编译器忽略,即函数不会展开。
(3.vs编译器debug版本下面默认是不展开inline的,这样方便调试。
(4.inline不建议声明和定义分离到两个文件,分离就会导致inline被展开,就没有函数的地址(因为inline函数不会形成栈帧地址),链接时会出现报错(头文件的声明会将函数汇总到符号表中,因为只有声明,没有有效地址,不能找到函数的定义)。
// F.h
#include <iostream>
using namespace std;
inline void f(int i);
// F.cpp
#include "F.h"
void f(int i)
{
cout << i << endl;
}
// main.cpp
#include "F.h"
int main()
{
// 链接错误:⽆法解析的外部符号 "void __cdecl f(int)" (?f@@YAXH@Z)
f(10);
return 0;
七.nullptr关键字
C语言中我们知道NULL,被定义为无类型指针(void*)的常量,但在C++中NULL可能被定义为字面常量0。而nullptr关键字是一种特殊类型的字面量,它可以转换成任意其他类型的指针类型。使用nullptr定义的空指针可以避免类型转换的问题,因为nullptr只能隐式转换为指针类型,而不能被转换为整型。
#include<iostream>
using namespace std;
void f(int x)
{
cout << "f(int x)" << endl;
}
void f(int* ptr)
{
cout << "f(int* ptr)" << endl;
}
int main()
{
f(0);
f(NULL);
f((int*)NULL);
// 编译报错:error C2665: “f”: 2 个重载中没有⼀个可以转换所有参数类型
// f((void*)NULL);
f(nullptr);
return 0;
}
结果如下。

26万+






