【C++】—— 从 C 到 C++ (上)

9毫米的幻想 2024-07-24 09:05:04 阅读 79

【C++】—— 从 C 到 C++ (上)

一、第一个C++程序二、命名空间2.1、命名的烦恼2.2、命名空间的定义2.2.1、定义命名空间2.2.2、命名空间的嵌套2.2.3、命名空间的同名

2.3、命名空间的使用2.3.1、指定命名空间的访问2.3.2、展开命名空间2.3.3、展开某个成员2.2.4、总结

三、C++的输入&输出3.1、输入输出的基本概念3.2、C++ 的输出3.3、C++ 的输入

四、缺省参数4.1、缺省参数的定义4.2、缺省参数的使用4.3、全缺省与半缺省4.3.1、全缺省4.3.2、半缺省

4.4、注意事项4.5、缺省参数的应用

五、函数重载

一、第一个C++程序

我们来写一下我们的第一个 C++ 程序:

<code>#include<stdio.h>

int main()

{

printf("hello world\n");

return 0;

}

大家是不是很奇怪,这 C++ 怎么一股 C 的味道

其实 C++ 是兼容 C 语言的。C++ 本来就是在 C 的基础上增加许多东西。

在写 C++ 代码时,要把文件后缀名改为

.

c

p

p

.cpp

.cpp,这样 VS编译器 就会调用 C++ 的编译器

那我们想写一段 C++ 自己的代码怎么写呢?我们来看看。

#include<iostream>

using namespace std;

int main()

{

cout << "hello world" << endl;

return 0;

}

这段代码可能有些小伙伴看不懂,没关系,看完本篇文章就解惑啦。

二、命名空间

2.1、命名的烦恼

在写 C语言 的代码时,如果我们定义了在全局变量定义了一个变量名为:

r

a

n

d

rand

rand,一般来讲是可以正常使用的,但当包含了头文件 <

s

t

d

i

o

.

h

stdio.h

stdio.h> 时,就会发现报错。

#include<stdio.h>

#include<stdlib.h>

int rand = 10;

int main()

{

printf("%d\n", rand);

return 0;

}

在这里插入图片描述

这是因为在 C语言 中规定在 同一个域中不能定义同名的东西,因为区分不开(C++也是如此)

上述代码中头文件 <

s

t

d

l

i

b

.

h

stdlib.h

stdlib.h> 包含了了库函数

r

a

n

d

rand

rand ,现在又在全局域中定义了<code>相同名字的变量,就会发生重定义,编译器无法识别你究竟想调用哪个。

我们写一个东西,本来写的好好的,但包了一个头文件之后突然就不行了,是不是很不爽

注:如果将

r

a

n

d

rand

rand 变量定义在

m

a

i

n

main

main 函数中(函数局部域)是不会报错的。因为他们不再同一个域中,而且根据就近原则,当全局域和函数局部域中定义了相同名字的变量时,编译器会直接调用 函数局部域 中定义的那个变量。

不仅如此,在工作中,往往是项目组的成员共同协作。小明和小红各自写了一份代码,都没问题,但两人代码一合并,就出现了大量的重命名。怎么办呢?这时小明只能发挥绅士风度,含泪把自己的代码改了。

通过上述两个例子,不难发现重命名这事还是很烦的,因此 C++ 中引入了命名空间来解决这个问题

2.2、命名空间的定义

定义命名空间,需要使用到

n

a

m

e

s

p

a

c

e

namespace

namespace 关键字,后面跟命名空间的名字,该名字根据程序员自己的实际需求或喜好定,然后加上一对 { } 即可( { } 后 不需要 加),{ } 中定义的成员即为命名空间的成员。命名空间中可以定义变量、函数、类型

n

a

m

e

s

p

a

c

e

namespace

namespace 本质是定义一个,这个域跟全局域各自独立不同的域可以定义同名的变量C++ 中有函数局部域全局域命名空间域类域;域影响的是编译时语法查找一个变量 / 函数 / 类型的出处(声明或定义)的逻辑,所以有了域的隔离,名字冲突就解决了。局部域和全局域会影响编译查找逻辑,还会影响变量的生命周期,命名空间域和类域不影响变量的生命周期

n

a

m

e

s

p

a

c

e

namespace

namespace 只能定义在全局,当然他也可以嵌套定义项目工程中多个文件定义的同名

n

a

m

e

s

p

a

c

e

namespace

namespace,会认为是一个

n

a

m

e

s

p

a

c

e

namespace

namespace,不会发生冲突C++ 标准库都放在⼀个叫

s

t

d

std

std(

s

t

a

n

d

a

r

d

standard

standard) 的命名空间中。(C++ 官方也怕和别人冲突,嘿嘿)

2.2.1、定义命名空间

那么现在,我们用命名空间解决上述问题吧

#include<stdio.h>

#include<stdlib.h>

namespace ganyu

{

int rand = 10;

}

int main()

{

printf("%d\n", rand);

return 0;

}

在这里插入图片描述

好消息:能打印了;

坏消息:不知道打印了个啥

其实,在这里编译器默认打印的是全局域中的

r

a

n

d

rand

rand。我们将自己定义的

r

a

n

d

rand

rand 变量放入了命名空间,相当于用一堵墙将其隔开,<code>编译器在默认情况下是不会去命名空间中找的,此时区局域中只有库函数的

r

a

n

d

rand

rand,程序自然能跑起来了(程序打印的是函数指针)。

那如何让编译器去命名空间中找呢?这时我们就需要认识一个新的操作符:: : 类作用域操作符

使用方法如下:命名空间名 :: 变量/函数/类型

#include<stdio.h>

#include<stdlib.h>

namespace ganyu

{

int rand = 10;

}

int main()

{

//使用::让编译器区命名空间查找

printf("%d\n", ganyu::rand);

return 0;

}

在这里插入图片描述

2.2.2、命名空间的嵌套

在工作中,往往会分为几个部门。每个部门下面又有若干个项目组,每个项目组又有若干个成员,每个项目组会有一个命名空间,该项目组中每个成员也可以在里面嵌套定义自己的命名空间

<code>

namespace ganyu

{

namespace cw

{

struct Node

{

int val;

struct Node* next;

};

}

namespace cq

{

int Add(int x, int y)

{

return x + y;

}

}

}

变量的访问方法如下:

int main()

{

struct ganyu::cw::Node newnode = { 0 };

ganyu::cq::Add(10, 20);

return 0;

}

可以看到使用了两次:: 操作符来调用

注:上述结构体的命名空间指定是加在

N

o

d

e

Node

Node 前而不是

s

t

r

u

c

t

struct

struct 前,因为

s

t

r

u

c

t

struct

struct 只是一个关键字

2.2.3、命名空间的同名

在一个工程项目中,同名的

n

a

m

e

s

p

a

c

e

namespace

namespace 不会冲突,他们会认为是一个命名空间。

当然,这个合并并不是真正的合并,而是逻辑上的合并

即使是自己一个人,一个工程也可能有多个文件,如果每个文件都单独要有一个命名空间,到时自己都记不住。有了这个性质,我们就很方便了

S

t

a

c

k

.

h

Stack.h

Stack.h

#pragma once

#include<stdio.h>

#include<stdlib.h>

#include<stdbool.h>

#include<assert.h>

namespace ganyu

{

typedef int STDataType;

typedef struct Stack

{

STDataType * a;

int top;

int capacity;

}ST;

void STInit(ST* ps, int n);

void STDestroy(ST* ps);

void STPush(ST* ps, STDataType x);

void STPop(ST* ps);

STDataType STTop(ST* ps);

int STSize(ST* ps);

bool STEmpty(ST* ps);

}

t

e

s

t

.

c

p

p

test.cpp

test.cpp

#include"Stack.h"

// 全局定义了⼀份单独的Stack

typedef struct Stack

{

int a[10];

int top;

}ST;

void STInit(ST* ps) { }

void STPush(ST* ps, int x) { }

int main()

{

// 调⽤全局的

ST st1;

STInit(&st1);

STPush(&st1, 1);

STPush(&st1, 2);

printf("%d\n", sizeof(st1));

// 调⽤ganyu namespace的

ganyu::ST st2;

printf("%d\n", sizeof(st2));

ganyu::STInit(&st2);

ganyu::STPush(&st2, 1);

ganyu::STPush(&st2, 2);

return 0;

}

2.3、命名空间的使用

2.3.1、指定命名空间的访问

在前面的代码中,我们访问命名空间中的成员就是用的指定命名空间的访问。通过命名空间加 ::,我们可以让编译器去指定的命名空间访问我们想要访问的成员。这里就不再举例介绍了。

2.3.2、展开命名空间

通过制定命名空间来访问,好是好,但可能有些小伙伴觉得每次都要使用 :: 操作符,太过繁琐,有没有简便的方法呢?有的,那就是展开命名名间

如果说命名空间是建立了一堵墙,保护里面的成员不被默认访问到,那么展开命名空间就是将这堵墙拆掉

命名空间的展开方式如下:

u

s

i

n

g

using

using

n

a

m

e

s

p

a

c

e

namespace

namespace 空间名;

注:虽然定义命名空间后面不用加,但是展开是要加

#include<stdio.h>

namespace ganyu

{

int a = 0;

int b = 1;

}

// 展开命名空间中全部成员

using namespace ganyu;

int main()

{

printf("%d\n", a);

printf("%d\n", b);

return 0;

}

可见,展开命名空间后就不用再使用::操作符啦

注:这里的展开命名空间和预处理中的展开头文件并不是一个意思

展开命名空间可是将命名空间这个域给拆开,可以理解成把这堵墙给推倒展开头文件是指头文件的内容拷贝过来

但是,凡事有利有弊。既然你把墙给推倒了,那么他就不能起保护作用了,这样又有了变量名相同的风险

2.3.3、展开某个成员

鲁迅先生说过:“中国人向来是喜欢折中的”

既然全展开和不展开都不行,那好,我们折中一下:展开部分成员

展开部分成员的方式如下:

u

s

i

n

g

using

using 空间名 : : 成员;

#include<stdio.h>

namespace ganyu

{

int a = 0;

int b = 1;

}

using ganyu::b;

int main()

{

printf("%d\n", ganyu::a);

printf("%d\n", b);

return 0;

}

2.2.4、总结

命名空间的三种使用方式:

指定命名空间访问项目中推荐这样方式。(虽然麻烦事麻烦了点,但是基本不会出岔子)

u

s

i

n

g

using

using 将命名空间中的某个成员展开:项目中经常访问的不存在命名冲突的成员推荐这种方式展开命名空间中的全部成员:项目中不推荐,冲突风险很大,日常小练习为了方便推荐使用

注:展开命名空间后再指定其成员时没问题的,当很少这样

三、C++的输入&输出

3.1、输入输出的基本概念

开头我们写了第一个 C++ 程序,不知大家还记不记得

#include<iostream>

using namespace std;

int main()

{

cout << "hello world" << endl;

return 0;

}

可能有许多第一次接触 C++ 的小伙伴看不懂,没关系,现在我们一起来了解

头文件 <

i

o

s

t

r

e

a

m

iostream

iostream>:<

i

o

s

t

r

e

a

m

iostream

iostream> 是

I

n

p

u

t

Input

Input

O

u

t

p

u

t

Output

Output

S

t

r

e

a

m

Stream

Stream 的缩写,即标准输入输出流库,定义了标准输入输出对象。可以把他的功能类比成 C 语言中的 <

s

t

d

i

o

.

h

stdio.h

stdio.h>

s

t

d

std

std::

c

o

u

t

cout

cout 是 ostream 类的对象,它主要面向窄字符

n

a

r

r

o

w

narrow

narrow

c

h

a

r

a

c

t

e

r

s

characters

characters(

o

f

of

of

t

y

p

e

type

type

c

h

a

r

char

char))的标准输出流,

类这个概念当前可先理解成结构体,之后的文章我们会进一步讨论

c

o

u

t

cout

cout 中的

c

‘c‘

‘c‘ 其实是

c

h

a

r

a

c

t

e

r

s

characters

characters,表示窄字符的输出;还有一个

w

o

u

t

wout

wout 表示宽字符的输出

s

t

d

std

std::

i

n

in

in 是

i

s

t

r

e

a

m

istream

istream 类的对象,它主要面向窄字符的标准输入流

为什么都是字符呢?其实像整型、浮点型等类型只有内存中才有,这是为了内存方便运算;到了文件、网络上就只剩下字符了其实无论我们用

p

r

i

n

t

f

printf

printf 还是

c

o

u

t

cout

cout,整型其实都是转换成字符再输出的。输出到哪呢?输出到控制台

c

o

n

s

o

l

e

console

console)、

L

i

n

u

x

Linux

Linux 中叫终端。如果需要输出到其他地方(如文件)则需要其他方式

<<流插入运算符>>流提取运算符(当然他们还做位左移/右移)

s

t

d

std

std::

e

n

d

l

endl

endl 是一个函数(很复杂),流插入输出时,相当于插入一个换行字符加插入缓冲区

注:

c

o

u

t

cout

cout、

c

i

n

cin

cin、

e

n

d

l

endl

endl 都是非常复杂的,它涉及类和对象,运算重载、继承等很多面向对象的知识,我们这里只是极其简单地讲一讲,往后的学习会更加深入了解他

C++ 的输入输出比 C语言 的更加方便,它不需要像 C语言 那样需要手动指定格式,C++ 的输入输出可以自动识别变量类型(本质是通过函数重载实现的),其实最重要的是 C++ 的流能更好的支持自定义类型对象的输入输出

3.2、C++ 的输出

我们直接上代码来感受一下C++ 的输出

#include<iostream>

int main()

{

int a = 10;

float b = 2.5f;

char c = 't';

std::cout << a << " " << b << " " << c << std::endl;

return 0;

}

运行结果:

在这里插入图片描述

而如果是 C语言 则要手动输入类型:

<code>int main()

{

int a = 10;

float b = 2.5f;

char c = 't';

//cout << a << " " << b << " " << c << endl;

printf("%d %f %c\n", a, b, c);

return 0;

}

在最后流插入时,换行用的是

e

n

d

l

endl

endl 那能不能像 C语言 一样用 ‘\n’ ,或 “\n” 呢?其实都是可以的,别忘了 C++ 是兼容 C 的

注:下列写法是不允许的:

std::cout << a" " << std::endl;

因为流插入操作符是个双目操作符,它只能有两个操作数,上述有三个操作数自然是不行的

可能有小伙伴会问:C++ 能不能在打印浮点数时控制打印精度,控制打印小数点后面多少位呢?

#include<iostream>

int main()

{

double a = 2.22222222;

printf("%.2lf\n", a);

return 0;

}

C++ 的默认识别类型默认是保留小数点后 5 位,要想实现控制精度 C++ 也能做到,但比较麻烦,要单独调用一个函数,这里更推荐大家直接用 C语言 的方法,毕竟 C++ 是兼容 C语言 的。

但凡事有利就有弊,既然 C++ 兼容了 C,那么也要付出一定的代价

简单提一下:由于兼容了 C,C 和 C++ 有各自的缓冲区,编译器两个都要关注,假如

p

r

i

n

t

f

printf

printf 在

c

o

u

t

cout

cout 前面,那么就要先刷新 C 的缓冲区才能打印

c

o

u

t

cout

cout 的内容

在一些高 IO 需求的地方,如部分大量输入的竞赛题中,可加上一下 3 行代码,可以提高 C++ 的 IO 效率

ios_base::sync_with_stdio(false);

cin.tie(nullptr);

cout.tie(nullptr);

3.3、C++ 的输入

下面我们来试试 C++ 的输入:

#include<iostream>

using namespace std;

int main()

{

int a = 0;

float b = 0.0f;

char c = 0;

cin >> a >> b >> c;

cout << a << "\n" << b << '\n' << c << endl;

return 0;

}

在这里插入图片描述

c

o

u

t

cout

cout /

c

i

n

cin

cin /

e

n

d

l

endl

endl 等都属于 <

i

o

s

t

r

e

a

m

iostream

iostream>,<

i

o

s

t

r

e

a

m

iostream

iostream> 是 C++ 的一个标准库,C++ 的标准库都放在一个叫

s

t

d

std

std(

s

t

a

n

d

a

r

d

standard

standard)的命名空间中,所以要通过命名空间的使用方式去使用他们在<code>日常练习中我们可以

u

s

i

n

g

using

using

n

a

m

e

s

p

a

c

e

namespace

namespace

s

t

d

std

std;,实际项目开发中不建议上述列举 C语言 代码时,我们没有包含 <

s

t

d

i

o

.

h

stdio.h

stdio.h>,也可以使用

p

r

i

n

t

f

printf

printf 和

s

c

a

n

f

scanf

scanf,这时因为在包含 <

i

o

s

t

r

e

a

m

iostream

iostream> 时,间接包含了 <

s

t

d

i

o

.

h

stdio.h

stdio.h>。VS 系列编译器是这样的,但其他编译器可能会报错。建议是先不额外包含 <

s

t

d

i

o

.

h

stdio.h

stdio.h>,程序报错再包含

四、缺省参数

4.1、缺省参数的定义

缺省参数是函数在声明或定义时为函数参数指定一个缺省值。在调用该函数时,如果该参数没有指定实参则用该形参的缺省值,反之使用指定的实参,缺省参数分为全缺省半缺省(有些地方吧缺省参数也叫做默认参数)

4.2、缺省参数的使用

#include <iostream>

using namespace std;

void Func(int a = 0)

{

cout << a << endl;

}

缺省参数很简单,只需在定义形参时给形参赋值(缺省值)即可

int main()

{

Func(); // 没有传参时,使⽤参数的默认值

Func(10); // 传参时,使⽤指定的实参

return 0;

}

运行结果:

在这里插入图片描述

4.3、全缺省与半缺省

缺省参数分为全缺省和半缺省:

<code>全缺省:全部形参给缺省值半缺省部分形参给缺省值

4.3.1、全缺省

例子如下:

void Func1(int a = 10, int b = 20, int c = 30)

{

cout << "a = " << a << endl;

cout << "b = " << b << endl;

cout << "c = " << c << endl;

}

C++ 中规定,带缺省参数的函数调用必须从左到右依次给实参,不能跳跃给实参

因此函数调用可以如下:

Func1();

Func1(1);

Func1(1,2);

Func1(1,2,3);

但是下面这样是不可以的

Func1(1, ,3);

Func1( ,2,3);

4.3.2、半缺省

C++ 中规定,半缺省中,必须从右往左依次缺省,不能跳跃给缺省值

如:

void Func2(int a, int b = 10, int c = 20)

{

cout << "a = " << a << endl;

cout << "b = " << b << endl;

cout << "c = " << c << endl << endl;

}

上述就是正确的缺省参数的定义方法

下面从左往右和中间跳跃都是错误

void Func2(int a = 0, int b = 10, int c);

void Func2(int a, int b = 10, int c);

void Func2(int a = 0, int b, int c = 20);

同样,半缺省的函数调用必须从左到右依次给实参,不能跳跃给实参

Func2(100);

Func2(100, 200);

Func2(100, 200, 300);

4.4、注意事项

当行数的定义和声明分开时,缺省参数不能在声明和定义中同时出现,规定必须在 函数声明 给缺省值

举个栗子:

S

t

a

c

k

.

h

Stack.h

Stack.h 文件

#include <iostream>

#include <assert.h>

using namespace std;

typedef int STDataType;

typedef struct Stack

{

STDataType* a;

int top;

int capacity;

}ST;

void STInit(ST* ps, int n = 4);

S

t

a

c

k

.

c

p

p

Stack.cpp

Stack.cpp 文件

#include"Stack.h"

// 缺省参数不能声明和定义同时给

void STInit(ST* ps, int n)

{

assert(ps && n > 0);

ps->a = (STDataType*)malloc(n * sizeof(STDataType));

ps->top = 0;

ps->capacity = n;

}

4.5、缺省参数的应用

下面我们通过栈来简单了解一下缺省参数的应用:

在我们初始化一个栈时,一般都是不开空间,或者开少量空间(比如现在开 4 个)。

现在我们明确知道到要开 1000 个空间。

想一想,如果没有缺省参数,是不是要不断扩容,差不多要扩容 10 次。要知道每次扩容都是有额外消耗的。

如果我们使用缺省参数,直接传实参 1000,是不是就是省去这部分消耗。

当然,这里只是简单举个例子,缺省参数在后面类和对象时会很好用,这点后面再介绍。

小甘雨说:做人不要做缺省参数,要做就做显示实参

五、函数重载

在写 C语言 中,大家是否遇到这样的情况:要对整型和浮点型进行加法运算,这时我们就要分别写一个整型的加法函数和浮点型的加法函数。加法函数的函数名我们一般是

A

d

d

Add

Add,但是因为 C语言 是不支持函数的重名的,这时我们便只能取两个函数名,比如

A

d

d

Add

Add

i

i

i /

A

d

d

Add

Add

f

f

f

在 C++ 中,引入了函数重载,即允许函数名相同。当然,也是有条件的

C++ 允许统一作用域中出现同名的函数,但是要求这些同名的函数形参不同

形参的不同可以是形参类型不同数量不同顺序不同

这样 C++ 的调用就表现出了多态行为,使用更加灵活

参数类型不同

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;

}

参数个数不同

void f()

{

cout << "f()" << endl;

}

void f(int a)

{

cout << "f(int a)" << endl;

}

参数类型顺序不同(本质也是类型不同)

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;

}

注:返回类型不同不符合函数重载,因为在调用时无法将他们区分

//不构成函数重载

void fxx()

{

;

}

int fxx()

{

return 0;

}

思考:下面两个函数构成重载函数吗?

void f1()

{

cout << "f()" << endl;

}

void f1(int a = 10)

{

cout << "f(int a)" << endl;

}

他们函数重载,满足形参数量不同

但是,调用他们会存在歧义,编译器会报错,因为当不传递参数时,编译器不知道要调用哪个

当然,如果给了参数那就没问题,这样就明确调用第二个函数

同时, 是没有办法区分他们的,除非使用命名空间。但是函数重载的定义是在同一域中,使用了命名空间他们就不是函数重载的概念了。我们只要不要这样写就好了。



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。