
◆ 博主名称: 晓此方-CSDN博客
大家好,欢迎来到晓此方的博客。
⭐️个人专栏:
◆数据结构系列
◆ C语言系列
◆C++系列
⭐️踏破千山志未空,拨开云雾见晴虹。 人生何必叹萧瑟,心在凌霄第一峰。
目录
0.1前言
初学C++很多人对于C++是什么,什么是面向对象程序设计,如何学好C++有很多困惑。本栏目将在接下来为大家系统完整的讲解。内容深入骨髓,细节无微不至。以真诚换真心。倾尽全力做到最好。还请大家多多三连支持!非常感谢!
0.2概述
C++是C++之父本贾尼博士在C语言的基础上创造出来的一种更为完善的计算机语言,解决了C语言的很多漏洞与缺陷。是现代社会生活中最重要的计算机语言之一。
一、命名空间(namespace)
1.1解决的C语言问题:命名冲突
C/C++语言为了避免编译歧义,规定:
(1)在同一个域内,不能出现两个相同的命名。
(2)在不同的域,允许出现两个相同的命名。
在公司项目开发中,不同的项目组在命名时往往会产生大量的命名冲突,此时就不可避免的要使用大量的条件编译,极大地拖慢了业务效率。
1.1.1例
cpp
#include<stdio.h>
#include<stdlib.h>
int rand=10;
int main()
{
printf("%d",rand);
retrun 0;
}
看代码,stdlib.h 头文件中有rand 函数(生成随机数)的定义,此时我们再定义一个rand变量并使用,就会出现编译错误。"error:重定义:以前的定义为函数"。
1.1.2域
分为全局域,函数局部域,命名空间域和类域。
1.2定义命名空间:
1.2.1原理
**方法论:**定义命名空间需要使用到namespace关键字,后面跟命名空间的名字然后加{},{}中的就是命名空间成员。命名空间可以定义变量/函数/类型等等。
**世界观:**namespace本质上定义了一个域,这个域和全局域互相独立,不同的域可以定义同名变量与全局域的函数rand的命名不再冲突。
这个时候我们创建一个命名空间域 ,把int rand 和函数rand(在全局域)隔离开来,避免了报错。
cpp
#include<stdio.h>
#include<stdlib.h>
namespace xcf
{
int rand=10;
}
int main()
{
printf("%d",rand);
retrun 0;
}
1.2.2出现错误的运行结果

这对吗? 事实上,我们打印的是全局域中的rand函数的指针。而不是我们设置的那个rand整型。
1.2.3根本原因:编译器的访问规则
|--------------------|--------------------|
| 访问对象只在全局域中出现 | 直接访问全局域 |
| 访问对象在全局域和函数局部域同时出现 | 就近访问原则,优先访问局部 |
| 使用域访问限定符时 | 直接访问域访问限定符限制区域 |
1.2.4域访问限定符的应用
1.2.4.1:符号和使用
(1), "::"域作用限定符号是两个冒号。
(2), "::"的左边是限定的域,右边是访问的变量/函数等。
(3), "::"左边什么都不写,表示访问全局域。
(4),"::"如果左边写一个域名:就会去这个域里面找。
1.2.4.2:实例一:对参数而言

1.2.4.3:实例二:对函数而言

1.2.4.4:实例三:对结构体而言
结构体与函数和变量稍有不同。规定不在struct Node前加域访问限定符。而是在结构体名称前加****因为我真正封装的结构体变量是Node而不是struct。

1.3命名空间嵌套定义与域合并
namespace可以嵌套定义。
1.3.1为什么我们引入嵌套定义
公司中,每一个部门会有多个项目组。
项目经理会规定不同的项目组使用不同类型的命名来避免冲突(不同项目组在各自的命名空间中命名了相同名称的函数,此时如果他们的命名空间也相同会引发严重后果)。
但是在同一个项目组中,两个不同的个人又可能存在命名空间冲突,于是为了避免这种情况
常常采用嵌套的命名空间。
cpp
namespace gsg
{
// 陈辉
namespace ch
{
int rand = 1;
int Add(int left, int right)
{
return left + right;
}
}
// 周航
namespace hg
{
int rand = 2;
int Add(int left, int right)
{
return (left + right) * 10;
}
}
}
看代码,陈辉在命名空间gsg 中定义ch 这个命名空间,周航在ghg这个命名空间中定义hg这个命名空间,避免了两者的冲突。
1.3.2,嵌套命名空间的内容调用
cpp
int main()
{
printf("%d\n", gsg::ch::rand);
printf("%d\n", gsg::hg::rand);
return 0;
}
先在gsg下面找到,ch/hg,再在ch/hg下面找到我想要的rand变量。
1.3.3,域合并
多个被定义的位于不同位置的命名空间会进行逻辑合并,即使他们在不同的文件中。
即:多个文件可以只创建一个命名空间,会认为是一个命名空间。
1.4命名空间展开
每次都指定命名空间xxx::实在有些麻烦。
于是我们引入了using关键字。
1.4.1全展开与部分展开
cpp
//全展开
using namespace gsg;
//部分展开
using gsg::rand;
展开整个命名空间:using+namespace关键字+命名空间名
展开部分命名空间:using+命名空间名+::+需要展开的内容
展开整个命名空间的风险很大但是方便,平时的小练习和个人项目是可以这么整的。
折中的方式:把某个成员展开:展开常用并且不存在冲突的成员。
1.4.2区分展开头文件和展开命名空间
展开命名空间和展开头文件不是一回事。
展开头文件是在预处理的时候在该include<>处拷贝该头文件的内容。
展开命名空间是指将该被展开变量从"默认去全局域查找"变成"到指定命名空间域中去查找"。
1.5命名空间细节补充与禁止操作
-
标准命名空间std:是 C++ 标准库中所有类、对象、函数和模板等元素所在的顶级命名空间。它将标准库的功能与用户自定义的代码隔离开来,防止名称冲突。
-
局部域和全局域会影响生命周期,命名空间域和类域不会影响。
-
只能在全局定义命名空间,不可以在局部定义命名空间。
-
一个域能定义很多东西。
-
命名空间域中的变量生命周期任然是全局的,它本质上还是全局变量。只是在查找的时候受到了限制。
-
命名空间不是结构体!后面没有分号。using展开语句后面有分号。
-
命名空间实际上可以反复无限嵌套的,但是不建议这么用。
二,输入和输出
2.1,解决C语言的问题:不支持自定义类型的输入输出
头文件:iostream(IO流)
- 是 Input Output Stream 的缩写。
- 类似于C语言的stdio.h,是标准的输入、输出流库,定义了标准的输入、输出对象。
- C++最老的版本,头文件是加了.h的。只有祖宗级别的编译器上才能看到(如VC6.0)。
- stdio.h和iostream的关系非常微妙,部分编译器上一脉相承,iostream包含了stdio.h,而某些编译器上并不支持。
2.2,输出流和流插入
2.2.1,输出流介绍
std::cout是输出流的一种,它可以把数据输出到控制台/终端。
除了cout,还有很多输出流。方便我们输出数据到文件/数据库等处。
2.2.2,输出原理
首先,cout的c是character的意思,在内存中,存储有整型,浮点数等类型的区分 ,但是在其他地方,比如说------控制台,它没有。------------即**,数据在输出时一律会被转换成字符再输出**。
cout输出到控制台正是将内存中的东西以字符的形式输出到控制台中。
2.2.3使用方式
cout输出流被定义在iostream头文件中,同时又被定义在标准流命名空间中。要将两者同时展开才能使用。
解释原理: cout被定义在iostream中,每一次使用需要包含这个头文件,头文件中的这个函数在预处理的时候被展开,但它并不是被展开在全局域中(像stdio.h那样)而是被展开在std标准命名空间中,只有使用命名空间域查找才能找到。

要么指定std::,要么using展开。不过,展开后同时指定的语法也可以。
2.2.4流插入
使用C++输入输出更方便,不需要像C语言的printf/scanf输入输出时那样,需要手动指定格式,C++的输入输出可以自动识别变量类型(本质是通过函数重载实现的在后面会讲到),其实最重要的是C++的流能更好的支持自定义类型对象的输入输出。
(自动识别变量类型包括指针类型)
2.2.4.1,流插入的特性:
无所谓格式并且可以随意多的插入,这是C++好用的地方。如代码:
cpp
int a = 0;
double b = 0.1;
char c = 'x';
cout << a << " " << b << " " << c << endl;
std::cout << a << " " << b << " " << c << std::endl;
2.2.4.2,空格和换行
插入" "表示空格,插入endl表示换行。(不要忘记endl被定义在std命名空间里面)
换行的另外几种方法(基于C++兼容C的小巧思)
cpp
cout << a << " " << b << " " << c << "\n" << '\n' << endl;
2.2.4.3严禁操作
cpp
cout << a " << b << "
"<<"是双目运算符,左边一个运算数:函数cout,右边一个运算数:插入的数。
2.3,输入流和流提取
有了以上的铺垫,输出流和流提取可以概括为一句话:
cin自动从控制台识别类型并提取数据>>变量,顺序是从cin由近到远。 cin后面不能用endl!
cpp
cin>>a>>b;
2.4输入输出细节补充
**(1)整个IO流体系,看不懂不要紧:**IO流涉及类和对象,运算符重载、继承等很多面向对象的知识,这些知识我们还没有讲解,所以这里我们只能简单认识一下C++ IO流的用法,后面我会专门出一篇来细节IO流库。

(2)**使用注意:**cout/cin/endl等都属于C++标准库,C++标准库都放在一个叫std(standard)的命名空间中,所以要通过命名空间的使用方式去用他们。
(3)警告: 一般日常练习中我们可以using namespace std ,实际项目开发中不建议using namespace std。
(4)精度控制小巧思: C++可以控制小数点,但是有点麻烦,需要精度控制函数:调用cout这个对象------>.调用精度控制函数precision。

但是C++兼容C,所以可以使用C语言的那一套更加方便的方法
cpp
double a=2.2222222222;
printf("%.2lf",a);
(5)提升效率小技巧: C++为了兼容C语言,在一些方面需要付出一些代价。如缓冲区绑定,缓冲区多次刷新。影响性能。在高IO需求的情况下可能不通过。
解决办法一: 直接使用printf和scanf。
解决办法二: 加入以下三行代码,具体什么原理以后会讲
cpp
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
三,缺省参数
缺省参数是声明或定义函数时为函数的参数指定一个缺省值。在调用该函数时,如果没有指定实参则采用该形参的缺省值,否则使用指定的实参,缺省参数分为全缺省和半缺省参数。(有些地方把缺省参数也叫默认参数)
3.1样例解析
cpp
void Func(int a = 0)
{
cout << a << endl;
}
int main()
{
Func(); // 没有传参时,使用参数的默认值
Func(10); // 传参时,使用指定的实参
return 0;
}
在形参的后面加上=0默认值 ,在调用这个函数的时候,如果我们,没有传递任何参数,那么采用这个缺省参数 。否则使用传递的参数。
3.2全缺省和半缺省
全缺省就是全部形参给缺省值,半缺省就是部分形参给缺省值。
cpp
// 全缺省
void Func1(int a = 10, int b = 20, int c = 30)
{
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl << endl;
}
// 半缺省
void Func2(int a, int b = 10, int c = 20)
{
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl << endl;
}
3.3缺省参数逻辑和调用给参逻辑
3.3.1核心逻辑:
• 怎么设置默认值(设参逻辑):
- 默认值必须从右边开始连续地给。
- 比如:func(a, b, c=3, d=4) 或 func(a, b=2, c=3, d=4) 是对的。
- func(a=1, b, c=3, d=4) 是错的,因为跳过了 b。
• 怎么传参数调用(调用逻辑):
- 你传参数的时候,从左边开始一个个给。
- 比如:func(1, 2) 就是给 a 和 b 赋值,c 和 d 用默认值。
- 你想给 d 赋值,就必须先把 a, b, c 都给了(或者 a, b 用默认值),不能跳着给。
总结一句话:设置默认值是"右边连续",调用传参是"左边开始"。
3.3.2一张图说明问题:

3.4缺省参数的应用
以初始化栈为例:
cpp
//声明
void STinit(ST*st,int n);
//调用
STinit(&st);
情况一,不知道要开辟多少空间**:**直接给一个缺省参数,不用去管n传递多少。
**情况二,已经知道要插入1000个数据:**这个时候直接传递n为1000。否则------可能不可避免要反复开辟空间(影响效率)。
两者配合起来------达到了"不知道就不传递,知道就传递"的效果。
3.5缺省参数的细节
函数声明和定义分离时,缺省参数不能在函数声明和定义中同时出现,规定必须函数声明给缺省值。
原因:声明和定义都给了,如果不一样会存在歧义。
四,函数重载
C++支持在同一作用域中出现同名函数 ,但是要求这些同名函数的形参不同 ,可以是参数个数不同或者类型不同 。这样C++函数调用就表现出了多态行为,使用更灵活。
C语言是不支持同一作用域中出现同名函数的。
4.1类型一:形参个数不同
cpp
// 2、参数个数不同
void f()
{
cout << "f()" << endl;
}
void f(int a)
{
cout << "f(int a)" << endl;
}
4.2类型二:形参类型不同
cpp
// 1、参数类型不同
int Add(int left, int right)
{
cout << "int Add(int left, int right)" << endl;
return left + right;
}
double Add(double left, double right)
{
cout << "double Add(double left, double right)" << endl;
return left + right;
}
4.3类型三:形参顺序不同
本质上是形参的类型不同
cpp
// 3、参数类型顺序不同
void f(int a, char b)
{
cout << "f(int a,char b)" << endl;
}
void f(char b, int a)
{
cout << "f(char b, int a)" << endl;
}
4.4特别注意情况:不建议缺省参数和重载共同使用
cpp
// 下面两个函数构成重载
// f()但是调用时,会报错,存在歧义,编译器不知道调用谁
void f1()
{
cout << "f()" << endl;
}
void f1(int a = 10)
{
cout << "f(int a)" << endl;
}
原理:如果这个时候去调用f1()函数,没有传递参数,可能调用下面这个,存在歧义。不要这么写。
如果坚决要这么做,只能将他们放在不同的命名空间里面,此时这就不是函数重载了,(原因是:函数重载需要在同一个域中实现)
函数重载的最后一个小问题:返回值不同不构成重载------关键是调用歧义的问题。
感谢你的收看,如果内容对你有帮助,不要忘了三联支持哦。