1.前言
C++是在C语言的基础之上,容纳进去了面向对象的编程思想和泛型编程,并增加了许多有用的库,以及一些关键字
本节主要介绍C++对于C语言语法不足的补充,以及C++是如何对C语言设计不合理的地方进行优化的,为后续学习类和对象打下基础
首先我们先看一下C++如何打印"hello world"
下面我们会介绍一下
#include <iostream> using namespace std;
这两行代码的作用
2.命名空间
1.C语言对于命名空间方面的缺陷
首先因为C++是兼容C语言的,所以在C++程序里面是可以运行C语言代码的,如图是在C++文件中运行C语言代码
可是.如果我们定义了一个变量或者函数,跟库中的某一个变量或者函数重命名了(即发生了命名冲突问题)会发生什么状况呢?
下面我们来看一下
我们定义了一个变量rand(注意:在stdlib.h头文件中有一个函数叫做rand)
在还没有包括stdlib.h头文件时,程序是可以正常运行的
因为此时还没有发生命名冲突
那么当我们包括了这个头文件时呢?
报错:rand重定义,以前的定义是函数
所以,对于项目开发而言,我们要定义的变量和函数肯定会特别多
我们就算不跟库中的命名发生冲突,也难免会发生与我们一起开发某个项目的同事的命名冲突的问题
如果发生了命名冲突问题,那么修改名字会非常麻烦,会影响项目开发的进度,这是C语言的一个非常大的缺陷
所以C++引入了命名空间这一语法
2.命名空间的语法特性
1.域作用限定符
namespace 命名空间的名称 { 可以放入int,double,char....... 函数,结构体,类等等所有我们可以定义的类型 }
如图,现在我们把我们定义的rand这个整形变量放入了命名空间wzs中,那么此时就不会跟stdlib.h头文件中的rand函数发生命名冲突了
命名空间就像是一个围墙,把rand这个整型变量"封"起来
防止非法访问
默认情况下编译器并不会去命名空间中进行查找,只有指定时才会到该命名空间里面去找
那么如果我们想要访问命名空间wzs中的rand呢?
想要访问呢?wzs::rand来访问
这个::叫做域作用限定符
同理,wzs命名空间中也可以定义函数,结构体等等…
下面我们来看一下
这里请注意,当我们想要访问wzs命名空间下的结构体SListNode时
需要把命名空间的名称放到struct的后面
struct wzs::SListNode* plist=NULL;
2.命名空间的可嵌套性
如果我们想要定义的数据太多了,出现了一个命名空间中的命名冲突问题呢?
此时我们可以嵌套命名空间
还可以在wzs1或者wzs2中继续嵌套,就像是套娃一样…但是最好不要嵌套太深,否则麻烦的还是自己
那么对于那种在头文件中声明,在源文件中定义的情况呢?
3.声明与定义分离的命名空间
我们定义了一个栈,放到了wzs命名空间中
这里就先不去全部写完了,毕竟我们这节课介绍的不是栈的代码实现
而是命名空间
请注意,我们将这里的命名空间也命名为wzs会报错吗?
答案是:并不会,因为不同文件下的命名空间会被编译器合并到一起,所以并不会报错
Stack.h #pragma once #include <stdio.h> #include <stdlib.h> //这里的命名空间也命名为wzs会报错吗?并不会 //不同文件下的同名命名空间会被编译器合并到一起,所以并不会报错 namespace wzs { struct Stack { int* a; int top; int capacity; }; void StackInit(struct Stack* ps); void StackPush(struct Stack* ps, int num); }
Stack.cpp #include "Stack.h" namespace wzs { void StackInit(struct Stack* ps) { ps->a = NULL; ps->capacity = ps->top = 0; } void StackPush(struct Stack* ps, int num) { if (ps->capacity == ps->top) { int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2; int* tmp = (int*)realloc(ps->a,sizeof(int) * newcapacity); if (tmp == NULL) { perror("realloc fail"); exit(-1); } ps->a = tmp; ps->capacity = newcapacity; } ps->a[ps->top++] = num; } }
TestStack.cpp #include "Stack.h" int main() { struct wzs::Stack st; wzs::StackInit(&st); wzs::StackPush(&st, 1); wzs::StackPush(&st, 2); wzs::StackPush(&st, 3); wzs::StackPush(&st, 4); wzs::StackPush(&st, 5); return 0; }
我们调试一下,程序运行正确
4.命名空间的展开
但是总是写命名空间真的好麻烦
所以能不能有一些情况下不用每次都指定命名空间呢?
using namespace wzs;//展开命名空间:默认指定在wzs这个命名空间下使用(把wzs这个墙拆掉) //请注意:这跟展开头文件完全不同,尽管都叫做展开
那么什么情况下我们可以展开呢?
一般情况下不要展开,除非自己写好做测试,没有给别人用,不存在冲突
所以一开始写的using namespace std;(这里的std是C++官方库定义的命名空间) 的意思是 展开C++官方库定义的命名空间
但是在工程项目中不要展开std,容易发生命名冲突
但是在我们自己日常练习的时候为了方便可以展开
5.多个命名空间中命名冲突
如果我们展开的两个命名空间之间本来就有命名冲突呢?
此时会报错
在加上域作用限定符之后就不会报错了
6.对于命名空间的推荐写法
在工程项目中,我们推荐这样使用
比方说我们现在想要很方便的使用使用C++中的cout这个对象,就可以这样去做
(因为cout,cin,<<(流插入运算符),>>(流提取运算符)
涉及到类和对象的知识,我们目前无法展开详细介绍原理)
(endl(end line)相当于C语言中的换行符"\n")
//在工程当中推荐这种写法 using std::cout; using std::endl; using std::cin;
我们不建议直接展开std命名空间
但是每次指定cout和endl,cin的命名空间很不方便,而直接展开就会把std全部暴露出来出现冲突风险
所以我们推荐这种指定展开的写法
所以我们就可以在使用cout,cin,endl的时候不用去指定命名空间了
注意:使用cout,cin,endl必须包含头文件iostream
3.iostream
C++中的iostream头文件类似于C语言中的stdio.h头文件
也就是标准IO头文件
C语言中的printf被cout取代,scanf被cin取代,“\n"被endl取代
一般情况下cout,cin,endl比printf,scanf,”\n"更加方便
不过请注意,有一些情况下它们三个也并不方便,(比方说cout对于浮点数精度的控制方法就不如printf方便)
因为C++是兼容C语言的,所以在这个时候可以使用C语言中的printf,scanf,“\n”
1.cout和endl
cout这里的c的意思:console(控制台/终端)
在这里我们没有展开cout,endl
发现使用cout和endl必须加上std::,太麻烦了,所以在工程中展开我们推荐那种写法
2.cin
3.缺省参数
缺省参数的好处是:可以让函数传参更加灵活,这也是C++设计者设计缺省参数的一个原因
1.缺省参数的形式
诸如这样的函数,它的所有形参都被赋予了默认值,这种情况情况下叫做全缺省参数 void f(int a=1,int b=2,int c=3); 诸如这样的函数,它的形参有一部分被赋予了默认值,这种情况下叫做半缺省参数 void f(int a,int b=2,int c=3); 注意:缺省值必须从右向左给,不能这样给: void f(int a=1,int b=2,int c); 因为如果我有这么一个函数调用 f(4,5); 就算是5对应的是c,但是4对应的到底是a还是b呢? 这样会产生歧义,因此C++语法规定缺省值必须从右向左给 缺省参数不能跳着给: void f(int a=1,int b,int c=3); f(4,5); 不知道给b的是4还是5
2.缺省参数举例
//全缺省参数 void f(int a = 10, int b = 1, int c = 3) { cout << a << " " << b << " " << c << endl; } //半缺省参数 void g(int a, int b = 1, int c = 3) { cout << a << " " << b << " " << c << endl; } int main() { f(); f(1); f(1, 2); f(1, 2, 3); //不过请注意:缺省参数不能跳着给,例如这样 //f(, 2, );//err //g();//err:函数中调用的参数太少 g(1); g(1, 2); g(1, 2, 3); return 0; }
可见:缺省参数的好处是:可以让函数传参更加灵活,这也是C++设计者设计缺省参数的一个原因
3.缺省参数"坑点"
注意:缺省参数不能在声明和定义中同时存在
为什么?
因为要防止出现这种情况:
Stack.h:
Stack.cpp:
哪怕我不用这个缺省参数,自己传了一个实参,都会报错
也就是怕出现定义和声明中的缺省参数值不同的情况
那么这时缺省参数到底听.h的还是.cpp的呢?会产生歧义,因此C++语法不允许缺省参数在定义和声明中同时出现
那么如果我今天就是想要在一个声明和定义分离的一个程序中设计一个含缺省参数的函数
怎么办?
只能在声明中加上缺省参数,定义中不允许加上缺省参数
因为当调用该函数的源文件包含了头文件之后
1.如果缺省参数是设置在定义中,而不是声明中,那么我在这个调用该函数的源文件中是看不到实际的缺省参数的
2.如果缺省参数是设置在声明中的话,那么是可以看到实际的缺省参数的
下面给大家展示一下:
这是将缺省参数设置在定义中的情况
这是将缺省参数设置在声明中的情况