文章目录
自学网站
写在前面
一、关键字
我们知道C语言有32个关键字,那么C++有几个关键字呢?答案是C++有63个,C++在C语言的基础之上又补充了21个关键字,这63个关键字分别是:
这一小节主要是让大家简单了解一下,放心吧,后面会专门详细讲解C++关键字的。
二、命名空间
1.命名冲突
看下面一段C语言代码:
#include<stdio.h>
int rand = 0;//定义一个全局变量rand
int main()
{
printf("%d\n", rand);
return 0;
}
程序正确执行:
但是!如果加上stdlib.h这个头文件呢?
代码如下:
#include<stdio.h>
#include<stdlib.h>
int rand = 0;//定义一个全局变量rand
int main()
{
printf("%d\n", rand);
return 0;
}
这个时候执行程序会报错:
这什么情况呀,为什么一开始还好好的,加上stdlib.h头文件之后就报错了呢?毋庸置疑的是肯定跟 stdlib.h 头文件有关系,但是具体有什么关系还得继续往下看:
#include<stdlib.h>
之所以加上这行代码之后报错,是因为在stdlib.h头文件里有一个生成随机数的库函数 rand ,结构如下:
因为在程序编译的时候,头文件会展开,也就会将头文件中所有的内容都放出来,此时同一作用域内出现两个同名变量,编译器会报错。这儿,便是命名冲突。
注意哦:
由于C语言没有很好地解决命名冲突的问题,所以C++为了解决它,特意引入 namespace
2.namespace关键字
在C++中,变量、函数和类型都是大量存在的,如果全部存在于全局作用域中,可能会导致许多冲突。
使用命名空间的目的就是对标识符的名称进行本地化,以避免命名冲突或名字污染,namespace关键字的出现就是为了解决这个问题的。
好,铺垫了这么多,如何用命名空间解决上述问题呢?请看下面一段代码:
#include<iostream>
#include<stdlib.h>
using namespace std;
namespace SL
{
int rand = 0;
}
int main()
{
printf("%d\n", SL::rand);
//::是域作用限定符,如果::左边没有SL,就默认是全局作用域
return 0;
}
这样程序就正确执行了,可能大家并不理解为什么这样程序可以跑起来,OK,不理解就对了,就是因为我们有很多不理解的地方所以才要学习呀,别害怕也别着急,都会讲到的,走着。
3.命名空间的定义
定义命名空间,需要使用namespace关键字,后面跟上命名空间的名字,然后接上一对大括号即可,{ } 中即为命名空间的成员,可以定义变量、函数以及类型。
· 普通命名空间
namespace SL //SL为命名空间的名称
{
//命名空间中的内容,既可以定义变量、函数,也可以定义类型
int a = 10;
int Add(int x, int y)
{
return x + y;
}
struct Node
{
int val;
struct Node* next;
};
}
· 嵌套命名空间
namespace byte
{
int a = 0;
//命名空间可以嵌套,一般多是2~3层
namespace data
{
struct Node
{
int val;
struct Node* next;
};
}
}
· 同名命名空间
同一个工程中允许存在多个相同名称的命名空间,编译器最后会合并在同一个命名空间中。
namespace byte
{
int a = 0;
namespace data
{
struct Node
{
int val;
struct Node* next;
};
}
}
namespace byte
{
int b = 0;
namespace cache
{
struct Node
{
int val;
struct Node* next;
};
}
}
注意事项:
- 一个命名空间就定义了一个新的作用域,命名空间中的内容都局限于其中;
- 命名空间不会影响变量的生命周期,而且命名空间的定义只能是全局的,不能在局部中定义命名空间,比如在main函数中定义就是错误的;
- 一般命名空间是定义在头文件下的。
对于命名空间的定义做一个总结:
1. 命名空间中可以定义变量、函数以及类型;
2. 命名空间支持嵌套,防止工程太大导致命名冲突;
3. 同名的命名空间是可以同时存在的,编译器编译时会自动合并。
4.命名空间的使用
还是采用这段代码:
注意看上面定义的命名空间哦,瞧好了,看下面如何使用:
using namespace byte;
//意思是把byte这个命名空间展开,将其中内容全部放出来
//但是需要注意的是嵌套的命名空间中的内容并没有放出来哦
using namespace byte;
using namespace data;
//这两行代码的意思是:先展开byte,再展开data
//如果颠倒这两行代码的顺序是不可以的,而且其意义不等同于using namespace byte::data;
using namespace byte::cache;
//注意哦,这里展开的是byte中的cache,并没有将byte展开
using的意义:
比如:
全部放出来:
using namespace byte;//将byte展开
部分放出来:
using namespace byte::a;//将byte中的变量a放出来
注意哦,只有放出来才能使用哦,否则还局限于命名空间中。
命名空间的三种使用方式:
· 加命名空间名称及作用域限定符
printf("%d\n", byte::a);
//这样就可以直接打印byte中的a
· 使用using将命名空间中成员引入
通常都是把部分常用的放出来。
using byte::a;//将byte中的变量a放出来
int main()
{
printf("%d\n", a);
}
· 使用using namespace命名空间引入
这种使用方式直接将命名空间中的内容全部放了出来。
using namespace byte;
int main()
{
printf("%d\n", a);
printf("%d\n", b);
}
对于第3种使用方式,我们最常用的就是:
using namespace std;
这里首先跟大家说一下,这样的使用方式是存在风险的,为什么这样说呢,下面补充一条知识点:
std 是C++标准库的命名空间,将它里面的内容全部放出来虽然方便我们使用了,但是存在冲突风险,所以我们在写一些C++ IO 型题目不在乎冲突的时候可以全部放出来,平时学习C++的时候最好别全部放出来,放出部分常用的即可,这点在后面你就可以体会到了。
三、C++输入&输出
我们在学习C语言的时候都是这样向世界问好的:
那么C++是如何向世界打招呼的呢?
其中 ‘endl’ 相当于 ‘\n’,看到了吧,这里我并没有将 std 中的内容全部放出来,而是只把常用的cout 和 endl 放了出来。
对C++的输入&输出进行说明:
- 使用 cout 标准输出和 cin 标准输入时,必须包含 iostream 头文件以及 std 标准命名空间;
- 使用C++输入输出更方便,不需要增加数据格式控制,如%d, %c这些。
补充一条小知识点:
四、缺省参数 | 默认参数
缺省参数又叫默认参数,C++支持缺省参数,但是C语言不支持。那么什么又是缺省参数呢?
举一个现实生活中很形象很可悲的例子:
没听懂?不要紧,再朝下看你就懂了(坏笑)
1.缺省参数的定义
缺省参数是声明或定义函数时为函数的参数指定一个默认值。在调用该函数时,如果没有指定实参就采用该默认值,否则使用指定的实参。
比如下面一段代码:
#include<iostream>
using std::cout;
using std::endl;
void TestFunc(int a = 10)
{
cout << "a = " << a << endl;
}
int main()
{
TestFunc();//没有传参时,使用参数的默认值
TestFunc(20);//传参时,使用指定的实参
return 0;
}
生动来说的话就是:
TestFunc();//没有传参时,使用参数的默认值
//喜羊羊不在,沸羊羊登场(默认参数)
TestFunc(20);//传参时,使用指定的实参
//喜羊羊回来了,没有沸羊羊什么事了
总结:
2.缺省参数的分类
· 全缺省参数
全缺省参数就是参数中全部给了缺省值。比如下面的函数:
void TestFunc(int a = 10, int b = 20, int c = 30)
{
cout << "a = " << a << endl;
cout << "c = " << c << endl << endl;
}
全缺省很简单,就不过多赘述了。
· 半缺省参数
注意哦,半缺省参数可不是参数中一半给了缺省值,哈哈,而是部分参数给了缺省值。
还有一个注意事项是:半缺省的必须从右往左缺省,中间不能有间隔,这是语法规定的。
比如下面这段代码是没有问题的:
void TestFunc(int a, int b = 20, int c = 30)
{
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl;
}
但是不允许出现下面这样的写法:
void TestFunc(int a = 10, int b = 20, int c)
{
……
}
这样也不可以:
void TestFunc(int a = 10, int b, int c = 30)
{
……
}
所以,这个时候你应该理解上面我所说的注意事项了吧。
再次强调一遍:
还有一个需要注意的地方:
缺省参数不能在函数的声明和定义的时候同时出现,只在函数声明的时候参数带有缺省值,函数定义的时候不能再重复出现了。(针对于函数的声明和定义分开写的情况,只有函数定义时可忽略)
为什么这样说呢,请看下面一段代码:
//Func.h
//函数的声明
void TestFunc(int a = 10);
//Func.cpp
//函数的定义
void TestFunc(int a);
为什么C++语法要这样规定呢?
打个比方吧:
你就说,此时此刻,小明该听谁的,该何去何从呢?OK,通过这个小故事,你应该理解为什么C++语法要规定说缺省参数不能在函数声明和定义中同时出现了。
着重强调一下:
3. 缺省参数的用处
前面说了这么多,我还是体会不到缺省参数有什么用,OK,我想想该用我们现在所学过的什么知识去举例子?有了,半缺省参数有一个很好的实例:
比如我们之前在学习数据结构——栈,我们用C语言代码是这样写的:
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
typedef struct Stack
{
int* a;
int top;
int capacity;
}Stack;
//初始化
void StackInit(Stack* ps)
{
assert(ps);
ps->a = NULL;
ps->top = 0;
ps->capacity = 0;
}
//入栈
void StackPush(Stack* ps, int x)
{
assert(ps);
//每次入栈都需要判断栈是否满了
if (ps->top == ps->capacity)
{
int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
int* tmp = (int*)realloc(ps->a, newCapacity * sizeof(int));
assert(tmp);
ps->a = tmp;
ps->capacity = newCapacity;
}
ps->a[ps->top] = x;
ps->top++;
}
int main()
{
Stack ST;
StackInit(&ST);
return 0;
}
但是上面的代码存在问题,每次入栈都需要判断栈是否满了,而且扩容是需要付出性能等方面的代价的。
C++的缺省参数就派上用场了,你瞧下面的代码:
#include<iostream>
#include<cassert>
#include<cstdlib>
struct Stack
{
int* a;
int top;
int capacity;
};
void StackInit(Stack* ps, int n = 4)
{
assert(ps);
ps->a = (int*)malloc(n * sizeof(int));
ps->top = 0;
ps->capacity = 0;
}
int main()
{
Stack ST;
StackInit(&ST, 100);
return 0;
}
当我们知道要开辟多大空间时,使用C++的缺省参数去初始化比C语言实现栈要优很多,而且直接在初始化的时候就可以将数组的空间开辟好。
缺省参数好用的地方还有很多,因为涉及到后面的知识,所以请铁子们耐住性子,后面还有很多接触它的机会呢。(狗头)
对缺省参数做一个总结:
- C++支持缺省参数,C语言不支持;
- 缺省参数不能在函数声明和定义时同时出现;
- 半缺省参数必须从右往左依次给出,不能隔着给;
- 缺省值必须是常量或者全局变量。
五、大厂面试真题
练习1
下面关于C++命名空间描述错误的是( )【单选】
A.命名空间定义了一个新的作用域。
B.std是C++标准库的命名空间。
C.在C++程序中,使用标准命名空间必须写成using namespace std;
D.我们可以自己定义命名空间。
解题思路
练习2
下面关于C++缺省参数描述错误的是( ) 【多选】
A.缺省参数是声明或定义函数时为函数的参数指定一个默认值.
B.在调用有缺省参数的函数时,如果没有指定实参则采用该默认值,否则使用指定的实参
C.C和C++都支持缺省参数
D.全缺省就是参数全部给缺省值,半缺省就是缺省一半的值
解题思路
六、函数重载中隐藏的细节
函数重载很重要,面试的时候常考,所以我会非常非常非常仔细地讲解底层原理,放心吧铁子们。
常考的面试题如下:
1.下面两个函数能形成重载吗?有什么问题,或者在什么情况下会出问题?
void TestFunc(int a = 10)
{
cout << "void TestFunc(int)" << endl;
}
void TestFunc(int a)
{
cout << "void TestFunc(int)" << endl;
}
2.C语言中为什么不能支持函数重载?
3.C++中对于函数重载底层是怎么处理的?
4.C++中能否将一个函数按照C的风格来编译?
这些问题在下一篇博文中都会详细讲解到,其实把底层讲清楚了之后,这些问题也就迎刃而解了。
注意,函数重载可不是在同一作用域下,函数名相同,参数不同这么简单哦,要想彻底明白它,前面还需铺垫一些知识,铁子们别担心,后面你会明白的。