【C++初阶】C++入门一(命名空间、输入&输出、缺省参数、函数重载等)

(1)前言

C++是C语言的继承,兼容绝大多数C的语法,在其基础上增加了一些语法,一般是为了解决C语言做不到的事情、修改不够好的地方

  • 什么是C++

C语言是结构化和模块化的语言,适合处理较小规模的程序。对于复杂的问题,规模较大的程序,需要高度的抽象和建模时,C语言则不合适。为了解决软件危机, 20世纪80年代, 计算机界提出了OOP(object oriented programming:面向对象思想),支持面向对象的程序设计语言应运而生。

1982年,Bjarne Stroustrup 博士在C语言的基础上引入并扩充了面向对象的概念,发明了一种新的程序言。为了表达该语言与C语言的渊源关系,命名C++。因此:C++是基于C语言而产生的,它既可以进行C语言的过程化程序设计,又可以进行以抽象数据类型为特点的基于对象的程序设计,还可以进行面向对象的程序设计。

  • C++的发展史
最重要的两个版本内容
C++98C++标准第一个版本,绝大多数编译器都支持,得到了国际标准化组织(ISO)和美国标准化协会认可,以模板方式重写C++标准库,引入了STL(标准模板库)
C++11增加了许多特性,使得C++更像一种新语言,比如:正则表达式、基于范围for循环、auto关键字、新容器、列表初始化、标准线程库等
  • C++的应用领域
  1. 操作系统以及大型系统软件开发
  2. 服务器端开发
  3. 人工智能
  4. 网络工具
  5. 游戏开发
  6. 嵌入式领域
  7. 数字图像处理
  8. 分布式应用
  9. 移动设备
  • 学习C++:建议不要把「精通C++」作为一个一年目标,应该要把学习语言作为一个持续的过程,同时要把语言运用在具体的应用场合中。

(2)C++关键字(C++98)

  • C++共有63个关键字,其中有32个关键字是我们在学习C语言时就见过的
  • 在接下来的学习过程中,学到某个关键字时再展开细讲
asmdoifreturntrycontinue
autodoubleinlineshorttypedeffor
booldynamic_castintsignedtypeidpublic
breakelselongsizeoftypenamethrow
caseenummutablestaticunionwchar_t
catchexplicitnamespacestatic_castunsigneddefault
charexportnewstructusingfriend
classexternoperatorswitchvirtualregister
constfalseprivatetemplatevoidtrue
const_castfloatprotectedthisvolatilewhile
deletegotoreinterpret_cast

(3)命名空间

  • C语言中存在的一个问题

在不同的作用域,我们可以定义同名的变量;在同一作用域,不能定义同名的变量。

所以在一个大项目中,多个人分工写代码,最后合在一起时,你写的代码和我写的代码中的命名可能会冲突,我们写的代码中的命名又可能会和标准库里的冲突。

假如张三写了个函数取名为 Add,我也写了个函数,取名也为 Add,代码合在一起时,可能会引起命名冲突等等。

  • 举例说明

如果没有包含 stdlib.h 头文件,代码可以正常运行;

但包含之后,因为 stdlib.h 库中有一个叫 rand 函数,全局变量 rand 与其冲突了。

#include<stdio.h>
#include<stdlib.h> /*rand*/

int rand = 10;  //全局变量rand

int main()
{
	printf("%d\n", rand);  //error: "rand"重定义,以前的定义是"函数"

	return 0;
}
  • C++提出了命名空间来解决命名冲突的问题

在C/C++中,变量、函数和类都是大量存在的,这些变量、函数和类的名称将都存在于全局作用域中,可能会导致很多冲突。使用命名空间的目的是对标识符的名称进行本地化,以避免命名冲突或名字污染,namespace 关键字的出现就是针对这种问题的。

命名空间相当于一个域,把各自写的东西隔起来。


1)命名空间的定义

  • 定义命名空间,需要使用到 namespace 关键字,后面跟命名空间的名字,然后接一对 { } 即可,{ } 中即为命名空间的成员。

  • 注意:一个命名空间代表定义了一个新的作用域,命名空间中的所有内容都局限于该命名空间中。

  • 命名空间的普通定义
//命名空间的普通定义
namespace L1  //L1为命名空间的名称
{
	//命名空间中可以定义变量/函数/类型
	int a = 10;
	
	int Add(int x, int y)
	{
		return x + y;
	}

	struct Node
	{
		struct Node* next;
		int val;
	};
}
  • 命名空间的嵌套定义
//命名空间的嵌套定义
namespace L2
{
	int a = 10;

	namespace L3  //嵌套定义一个名为L3的命名空间
	{
		int b = 20;
		int sub(int x, int y)
		{
			return x - y;
		}
	}
}
  • 同一个工程中允许存在多个相同名称的命名空间,编译器最后会合成到同一个命名空间中(所以相同名称的命名空间中不能出现同名的变量/函数/类型,否则会引起冲突)

2)命名空间的使用

//定义一个命名空间N1
namespace N1
{
	int a = 10;
	int b = 10;

	int Add(int x, int y)
	{
		return x + y;
	}
}

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

  • 全部展开到全局,就可以直接使用(使用 using namespace 将命名空间名称引入)
// 优点:用起来方便
// 缺点:把自己的定义暴露出去了,导致命名污染
//using namespace std;  //std是包含C++标准库的命名空间

using namespace N1;
int main()
{
	Add(1, 2);
	return 0;
}
  • 访问命名空间中的成员时,加命名空间名称及作用域限定符
// 优点:不存在命名污染
// 缺点:用起来麻烦,每个都需要去指定命名空间

int main()
{
	printf("%d\n", N1::a);
	N1::Add(1, 2);
	return 0;
}
  • 使用 using 将命名空间中常用的成员引入
// 优点:不会造成大面积的命名污染,又可以把常用的给展开
// 这是一个折中的解决方案

using N1::Add;  //命名空间N1中Add函数和变量a用的非常多,将其展开到全局
using N1::a;

int main()
{
	printf("%d\n", a);
	Add(1, 2);
	return 0;
}
  • 补充一点:命名空间 std 的规范使用

C++把标准库里面的东西都放到命名空间 std 中,所以在实际开发中,为了避免你自己写的变量/函数/类型等等与标准库中的冲突,建议不要把命名空间 std 直接展开到全局,而是把常用的展开就行,一般规范的写法为:

#include<iostream>
//using namespace std;  //不要将std直接展开到全局

//把常用的展开就行
using std::cout;
using std::endl;

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

如果是日常练习,就不需要像上面这么规范,直接展开用


(3)C++的输入&输出

C语言有自己的输入输出函数 scanf 和 printf,那么C++也有自己独特的输入输出方式,cin 标准输入流cout 标准输出流,必须包含 头文件和 std 标准库命名空间。

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

使用C++输入输出更方便,它可以自动识别变量的类型,不需增加数据格式控制,比如:整形–%d,字符–%c

#include<iostream>
using namespace std;  //C++标准库

int main()
{
	int a;

	cin >> a;  // >> 输入运算符/流提取运算符
	cout << a; // << 输出运算符/流插入运算符
	cout << endl;  //换行,等价于 cout << '\n';

	return 0;
}
  • 注意:C++中的浮点数,当小数点后数字超过 5 位时,cout 规定输出的是小数点后 5 位,如果想要实现浮点数的格式化输出,那就建议用 printf 来输出,使用一定要灵活变通。

    image-20211007164657831
    image-20211007165603597

(4)缺省参数

1)缺省参数的概念

**缺省参数是声明或定义函数时为函数的参数指定一个默认值。**在调用该函数时,如果没有指定实参则采用该默认值,否则使用指定的实参。

#include<iostream>
using namespace std;

void Func(int a = 0)
{
	cout << a << endl;
}

int main()
{
	Func();   //没有传参时,使用参数的默认值
	Func(10); //传参时,使用指定的参数

	return 0;
}
  • 缺省参数的使用

以前入栈时必须得判断一下容量是否为0,若为0,给capacity赋一个初始值,但赋的这个初始值没办法灵活的去控制,有了缺省参数,我们就可以根据需要,来控制初始容量。

#include<iostream>
using namespace std;

//定义一个顺序栈
struct Stack
{
	int* a;
	int top;
	int capacity;
};

//初始化栈,给栈容量设置一个默认值4
void StackInit(struct Stack* ps, int DefaultCapacity = 4)
{
	ps->a = (int*)malloc(sizeof(int) * DefaultCapacity);
	ps->top = -1;
	ps->capacity = 0;
}

//入栈
void StackPush(struct Stack* ps)
{
	if (ps->top == ps->capacity + 1)
	{
		ps->capacity *= 2;
		//......
	}
	//......
}

int main()
{
	//假如我明确知道这里至少要存100个数据到st1中去
	struct Stack st1;
	StackInit(&st1, 100);

	//假如我不知道要存多少个数据到st2中去
	struct Stack st2;
	StackInit(&st2);

	return 0;
}

2)缺省参数的分类

  • 全缺省参数
#include<iostream>
using namespace std;

void Func(int a = 10, int b = 20, int c = 30)
{
	cout << a + b + c << endl;
}

int main()
{
	//调用全缺省参数的函数方式很灵活
	Func();
	Func(1);
	Func(1, 2);
	Func(1, 2, 3);

	return 0;
}
  • 半缺省参数
#include<iostream>
using namespace std;

void Func(int a, int b = 10, int c = 20)
{
	cout << a + b + c << endl;
}

int main()
{
	//调用半缺省参数的函数
	Func(1);       //1传给a
	Func(1, 2);    //1传给a,2传给b
	Func(1, 2, 3); //1传给a,2传给b,3传给c

	return 0;
}
  • 总结
  1. 半缺省参数必须从右往左依次来给出,不能间隔着给
  2. 缺省参数不能在函数声明和定义中同时出现(建议声明的时候给缺省参数,定义的时候就不要给了)
//注意:如果声明与定义位置同时出现,恰巧两个位置提供的值不同,那编译器就无法确定到底该用那个缺省值。

//test.h
void Func(int a = 10);

//test.c
void Func(int a = 20)
{
	//......
}
  1. 缺省值必须是常量或者全局变量
//不能这样给缺省值
void Func(int a, int b = x);//error
  1. C语言不支持缺省参数(编译器不支持)

(5)函数重载(很重要)

自然语言中,一个词可以有多重含义,人们可以通过上下文来判断该词真实的含义,即该词被重载了。

以前有一个笑话,国有两个体育项目大家根本不用看,也不用担心。一个是乒乓球,一个是男足。前者是 ” 谁也赢不了!”,后者是 “ 谁也赢不了!”

可以看出,同一句话,可能有多重意思。

  • 函数重载的概念

函数重载是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数 / 类型 / 顺序)必须不同,常用来处理实现功能类似但数据类型不同的问题。

int Add(int a, int b)
{
	return a + b;
}

double Add(double a, double b)
{
	return a + b;
}

long Add(long a, long b)
{
	return a + b;
}

int main()
{
	Add(10, 20);
	Add(10.0, 20.0);
	Add(10L, 20L);  //L表示该常数是以长整型方式存储,long
	return 0;
}
  • 思考
  • 编译器能不能实现函数名相同,参数相同,返回值不同来构成函数重载?

结论:不能实现,无法重载仅按返回值类型区分的函数

int func();    ->  _Z3ifunc
double func(); ->  _Z3dfunc

如果把返回值带进名字修饰规则,那么编译器层面是可以区分的。

但是语法调用层面,无法根据参数列表确定调用哪个重载函数,甚至带有严重的歧义!

比如这条语句 func(); ,调用时,这里是要调用哪一个呢?


(6)extern “c”

C++编译器能识别C++函数名修饰规则,也能识别C的函数名修饰规则。

  • 我们在C++项目开发中,可能会用到一些第三方的库,有些是纯C实现的库(动态库/静态库),它里面的函数名修饰规则是C的,而C++是兼容C的,C++编译器直接按照C的修饰规则去调用。

  • 如果是纯C的项目,用到C++实现的库(动态库/静态库),比如:tcmalloc是google用C++实现的一个库,他提供 tcmallc()tcfree() 两个接口来使用(更高效,替代malloc和free函数),这个库可以给C++项目用,但不能给C项目用,会存在链接失败,因为C编译器无法识别C++的修饰规则。

  • 如果想要给C用,需要将C++库中的部分函数按照C的风格来编译,在函数前加 extern "C",意思是告诉编译器,将该函数按照C的修饰规则来编译

extern "C" void Add(int a, int b);
  • 总结
  1. C++项目可以调用C++的库,也可以调用C的库。

  2. C的项目可以调用C的库,如果要调用C++的库,需要在该函数前加上 extern "C"

  • 思考题
  1. 下面两个函数能形成函数重载吗?——> 不能

    void Func(int a = 10)
    {
    	cout << "Func(int)" << endl;
    }
    void Func(int a)
    {
    	cout << "Func(int)" << endl;
    }
    
  2. C语言中为什么不能支持函数重载?

    ——> C和C++编译器的函数名修饰规则不一样,C编译器直接拿函数名在目标文件里面充当函数名和函数地址的映射,所以在链接的时候,是拿函数名去目标文件里面去找,函数名相同,虽然参数不同,但不知道找的是谁。

  3. C++中函数重载底层是怎么处理的?

    ——> C++编译器不直接拿函数名去找,而是拿修饰后的函数名去找,参数不同修饰后的函数名就不同,就能够找到。

  4. C++中能否将一个函数按照C的风格来编译?

    ——> 能,在函数前加上 extern "C"


未完待续,下一篇,我们将学习C++中最重要的一个知识之一,引用 …… 一起期待吧!

评论 19
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值