初识C++

时间:2023-02-24 15:28:12

写在前面

这是我第一次接触到C++,之前对它的大名可谓是如雷贯耳.这两天有人问我学习C++的感觉怎么样?对此我想说,很难.C++的细节很多,要求我们比学习C语言更加细心,我不是没有学习过OOP语言,但是刚开始接触到还是感觉有点吃力,不过这是一种很令人高兴的情况,这代表你在进步,要是我们在一直学习已经站掌握的知识,又怎么会提高我们呢?我学习C++的方法是通过看网上一些大佬讲解的视频,然后通过一些经典的书籍来补充,后面和大家分享一下博客,做些OJ题.这种方法对我来说还是挺不错的,大家要是没有方法,可以先试试这个,后面可以选择更加适合自己的方法.


什么是C++

这里我就不和大家分享了,一般我们看每一本书都会谈到C++的历史,我想先和大家来一起学习这门语言,后面到最后专门写一篇博客来专门谈谈历史的一些趣闻趣事,这在我们面试的时候可以和面试官聊聊.

这里我先提一点,C++可以认为是对C语言一些缺陷的补充,C++编译器支持C语言语言的90%以上,类似函数的命名,scanf,printf都是支持的.

如何创建一个C++文件

前面我和大家分享C语言时用的环境大部分都是VS2019,这里用来做C++集成开发环境还是可以的,创建文件的步骤和之前是一样的,就是在最后一步,我们的文件的后缀名是.cpp,这样VS就会调用C++的编译器.

初识C++

Hello word

我们精通各种语言的Hello word,谈个玩笑.Hello word在我心中有不一样的意义,它敲开了很多人对计算机的大门,在这里我们仍旧打印出这句话,标志着我们正式进入C++,大家对代码要是有什么不理解的话,先不要着急,后面我们会一一解剖.

#include <iostream>
using namespace std;

int main()
{
cout << "hello world" << endl;
return 0;
}

初识C++

命名空间

我们总是看到有人一写代码就把using namespace std;,这是什么?这是必须做的吗?一般人可能就会说我们记住就可以了,不用管原理,但是对于我们这些要进大厂的人来说,这是很重要的.我们先来看一个例子,下面的代码可以跑过吗,为什么不能跑过?

void f() { } int f = 0; int main() { printf("%d", f); return 0; }

初识C++

我们可以看出,第一个f表示的是函数名,第二个是一个全部变量,这就给编译器一种误导,尽然会有两个一摸一样的人?这在语法上就是一种错误.在C语言这种错误只能有一种解决方法,函数改名或者是变量改名,这就会造成这样一种情况:

函数说:你改名字吧,变量,我这都被调用几百次了.变量反驳到,我这都用了上千次了,你说谁改?这种情况我们该怎么办,在C语言中我们我们只能一个一个修改.但是在C++中我们可以通过一种命名空间的东西是来解决.有人可能会感到纠结,我会这么大意,让它们重名?开玩笑!是的,我们自己当然可以确定自己的的命名不重复,可是大家不要忘了,一个很大的程序是多人协作共同完成的,你可以确定你们几个人甚至几十个人命名不重复,谁都不能保证.

namespace

C++解决命名重复就是使用namespace关键字,这是C++新增的,通过这个关键字我们可以把自己定义的变量隔离出来,就是像一个箱子一样,每一个人拥有一个属于自己箱子,用到的时候,自己打开自己的箱子就可以了.我们后面具体在谈,下面先看一个例子.

我们都知道下面的代码会打印出什么结果?这里我们都知道局部优先,结果就是0.但是这里我先要得到全局变量a的结果,也就是10,怎么弄?

int a = 10;

int main()
{
int a = 0;
printf("a = %d\n", a);
return 0;
}

域作用限定符 ::

我们可以通过一个::来解决这个问题,我们可以把命名空间看作是一个箱子,::就是打开箱子的动作.箱子:: 箱子里面的东西,当箱子被省略时,我们默认这个箱子就是全局域,它会去全局域那寻找.这就是我们可以得到10的原因.

int a = 10;

int main()
{
int a = 0;
printf("a = %d\n", ::a);
return 0;
}

初识C++

创建命名空间

现在我们就可以放开手和大家分享了,前面的铺垫一已经够我们现在继续说下去了.在一个大型的程序中,每一个人都可以创建自己的命名空间,这样的话我们几个人之间就可以不担心命名重复了,使用namespace就可以创建了.

张三和李四都独自创建了自己的命名空间,这样即使后面两个人使用用一个命名也不会报错

//张三的命名空间
namespace N1
{
int a;
}

//李四的命名空间
namespace N2
{
int a;
}
int main()
{
N1::a = 10; // 得到 N1空间里面的a
N2::a = 20;
printf("%d\n", N1::a);
printf("%d\n", N2::a);
return 0;
}

初识C++

这里我想谈谈下面几点,这些有助于我们理解命名空间

上面的变命名的变量是全局变量,在我们定义这两个变量后,会在内存中开辟不同的空间,由于我们是全局变量,编译器会自动赋值0.

int main()
{
printf("%p\n", &(N1::a));
printf("%p\n", &(N2::a));
return 0;
}

初识C++

命名空间里面可以有什么

这个问题很好,命名空间里面可以包含三类事物.

  1. 全局变量
  2. 函数
  3. 结构体

前面的全局变量已经和大家分享过了,这里从函数这个比较简单的说起.

和全局变量一样,我们为了防止函数命名重复,也可以把函数放到命名空间里面.

namespace byte
{
void func()
{
printf("这是 byte 空间里面呢的函数\n");
}
}

int main()
{
byte::func();
return 0;
}

初识C++

我们等会谈结构体,关于结构体,要是很上面说的一样简单就有点落于俗套了,这个我在命名空间重复嵌套这里谈,会好好说说.

命名空间的嵌套

一个大型工程,里面可能还要分为很多组别,例如有数据组的,有测试组的,这样的话,我们希望可以对命名空间进行嵌套,这样就可以避免重复

下面就是重复嵌套的命名空间

namespace byte
{
namespace data
{
int a;
}
namespace test
{
int a;
}
}
namespace byte
{
int a = 10;
namespace bit
{
int a = 20;
}
}

int main()
{
printf("%d\n", byte::a);
printf("%d\n", byte::bit::a);
return 0;
}


这里谈一谈,但我们使用命名空间存储结构体会发什么什么?注意这里的结构体里面存储的是类型,不是变量,就像我们在全局区那里自己定义类型一样,当然它可以存储结构体变量.下面的的命名空间我们我们可以嵌套几个,里面就可以定义我们的结构体了.

namespace byte
{
//数据组
namespace data
{
struct ListNode
{
int val;
struct ListNode* next;
};
}
//缓存组
namespace cache
{
struct ListNode
{
int val;
struct ListNode* next;
};
}
}

命名空间名字重复

当我们定义的命名空间名字重复后,编译器会自动把他整合在一起,也是说名字一样的命名空间可以看作同一个空间.

namespace N1
{
int a;
int b;
}
namespace N1
{
int a;
int b;
}

初识C++

解决办法 :: 嵌套一个命名空间,这里不谈了

问题,命名空间只能存在全局,不能在函数体内.

如何使用命名空间里面的元素

我想使用命名空间里面的元素,有没有办法内?

使用域作用限定符::

前面我们已经谈过域作用限定符::了,刚才的众多例子中也多次使用了这个域作用限定符,这个域作用限定符的作用就像是可以打开箱子一样,拿出里面的元素.这里具体谈谈.

namespace N1
{
int a;
int b;
}

int main()
{
N1::a = 1;
N1::b = 2;
printf("%d %d", N1::a, N1::a);
return 0;
}

初识C++

使用域作用限定符::,,我们使用哪个就可以拿出来哪个,这种方法比较谨慎,安全性也比较高,不过有一个缺点就是,有一点麻烦.

打开全部的元素

前面谈了有一点麻烦,我们每一次使用命名空间里面的额元素都要使用域作用限定符,这里有一个简单的方法,我们可以直接打开命名空间里面的全不元素,大家一看就知道了.

打开命名空间里面的所有元素,直接使用这些就可以了.这样相当于没有定义命名空间.

namespace N1
{
int a;
int b;
}

using namespace N1; //打开 N1 的命名空间
int main()
{
a = 20; //直接使用
b = 10; //直接使用
printf("%d %d", a, b);
return 0;
}

打开部分元素

打开全部的元素这种也会带来一部分问题,当我们打开多个命名空间的时候,既有可能会出现命名一样的情况,这里编译器就会报错,这里也有一个折中的方法,用哪个就把它从名宁空间里面拿出来.我们打开了变量 a,就可以把他放开了.

namespace N1
{
int a;
int b;
}

using N1::a; //不用带namespace

int main()
{
a = 20; //直接使用
N1::b = 10; //不能直接使用
printf("%d %d", a, N1::b);
return 0;
}

初识C++

我们这里来个测试,大家应该可以看懂了.

namespace byte
{
int a;
//数据组
namespace data
{
struct ListNode
{
int val;
struct ListNode* next;
};
}
//缓存组
namespace cache
{
int b;
struct ListNode
{
int val;
struct ListNode* next;
};
}
}
using namespace byte; // 必须先展开byte,要符合逻辑的循序
using namespace byte::data;
using byte::cache::b;
int main()
{
//用域作用限定符
byte::a = 10;
struct byte::cache::ListNode n1; //有没有struct都可以

//打开 byte命名空间
a = 10;
struct data::ListNode n2; //有没有struct都可以

//打开 byte命名空间 和 data
struct ListNode n3;

//使用 cache的的部分元素
b = 20;
return 0;
}

初识C++


解释using namespace std;

现在我们就可以知道了他就是打开名为std命名空间,这句话不是必须的存在的,我们也是这样用.

int main()
{
std::cout << "hello word" << std::endl;
return 0;
}

初识C++

这样我们知道如果全部展开会有一定的冲突风险,但是后面我们为了方便,先暂时这样做,等到项目时在指定展开使用.

C++输入和输出

我们都知道,C++ 支持C语言的很多语法,这里输入输出可以使用scanf和printf,但是C++也给出了一些其他的方法,而且更加方便,我们先来看看具体如何做.

#include <iostream>

using namespace std;

int main()
{
int a = 0;
double d = 0.0f;
cin >> a;
cin >> d;
cout << a;
cout << d;
return 0;
}

初识C++

注意:早期标准库将所有功能在全局域中实现,声明在.h后缀的头文件中,使用时只需包含对应头文件即可,后来将其实现在std命名空间下,为了和C头文件区分,也为了正确使用命名空间,规定C++头文件不带.h;旧编译器(vc 6.0)中还支持<iostream.h>格式,后续编译器已不支持,因此推荐使用**+std**的方式.

这里我要谈一些东西

  • 在使用pritnf时,我们需要指定数据的格式,但是上面这种方法不需要,编译器会自动识别并匹配.
  • cin是标准输入流,从键盘中中输入,cout是输出流,输出到屏幕上
  • \>>和<<可以理解为方向,例如 cin>>a,就是数据从键盘上输入到a中.cout<<a就是a输出到屏幕上
  • 大家疑惑插入操作符<<和左移操作符一样,这实际上是操作符的重载.我们现在不需要了解这么多,后面都会谈的

如何进行换行?,上面的结果我很不适应,可以换一个行吗? 可以的

using namespace std;

int main()
{
int a = 0;
double d = 0.0f;
cin >> a;
cin >> d;
cout << "a = " <<a << '\n';
cout << "d = " <<d << '\n';
return 0;
}

初识C++

缺省函数

我们开始疑惑,函数就是函数,什么叫缺省函数.不知道大家知不知道备胎,就是有事的时候找你来帮忙,没事的时候就吊着你.缺省函数也是如此,我们先来看看.

这里的func就是一个缺省函数,如果我们不传入参数,a就会赋值10,就是那个备胎,传入了,就可以把备胎给扔掉了,我们用自己的.

void func(int a = 10)
{
printf("a == %d\n", a);
}
int main()
{
func();
func(20);
return 0;
}

初识C++

缺省函数的应用

这里我给出一个应用,我们在之前分享数据结构的时候,经常会出现初始化的情况,这里我先义队列为例子

这样我们初始化时就不需要给函数传入多少个空间,一当完成初始化就会有4个数据的空间,第一次插入的时候就不需要扩容了.

typedef struct Queue
{
int* elem;
int cap;
int size;
} Queue;

void QueueInit(Queue* ps, int cap = 4)
{
ps->elem = (int*)malloc(sizeof(int) * cap);
ps->cap = cap;
ps->size = 0;
}
int main()
{
Queue qu;
QueueInit(&qu);
return 0;
}

多参数的缺省函数

只有一个函数的缺省函数可能体现不出来特性,下面我们使用三个参数的.

全部缺省

using namespace std;

void func(int a = 1, int b = 2, int c = 3)
{
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl;
}
int main()
{
func();
cout << endl;
func(111);
cout << endl;

func(111,222);
cout << endl;

func(111,222,333);
return 0;
}

初识C++

部分缺省

对于多参数的函数而言,我们可以缺省一部分的参数,不过参数缺省还需要一定的要求.

半缺省参数必须从右往左依次来给出,不能间隔着给

void func(int a,int b = 2, int c = 3) // 注意方向
{
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl;
}

初识C++

缺省函数的注意事项

我们来看看这些注意事项

  • 半缺省参数必须从右往左依次来给出,不能间隔着给
  • 缺省参数不能在函数声明和定义中同时出现
  • 缺省值必须是常量或者全局变量

第一个我们已经谈过了,这里主要谈第二条,我么缺省函数在声明的时候如何声明缺省函数.

void func(int a = 30);
void func(int a = 30)
{
cout << a << endl;
}

int main()
{
func();
return 0;
}

初识C++

当函数声明遇到函数声明和函数实现的时候,编译器不到使用哪个缺省值,即使有时他们是相同的.这样就会报错.这里我来谈谈

声明给,定义不给

我是使用的三个文件来演示的,

//Test.h文件中
#include <iostream>

using namespace std;

void func(int a = 20);

//test.cpp中
void func(int a)
{
cout << a << endl;
}

//mian.cpp中
#include "Test.h"

int main()
{
func();
func(11);
return 0;
}

初识C++

初识C++

声明不给,定义给

这种请情况会报错.

//Test.h文件中
#include <iostream>

using namespace std;

void func(int a);

//test.cpp中
void func(int a = 20)
{
cout << a << endl;
}

//mian.cpp中
#include "Test.h"

int main()
{
func();
func(11);
return 0;
}

初识C++

初识C++

这个时候我们就知道了以后我们使用缺省函数是我们在声明的时候给,定义的时候就不要给了.