【无标题】C++入门

一、命名空间

1.什么是命名空间

在c语言中,如果我们的程序包含某一头文件,那么我们就不能定义与其同名的全局变量,否则编译器就会出错;比如在下面的程序中,我们包含了<string.h>头文件,该头文件中含有trelen函数,如果再用strlen作为变量名定义,就会造成重定义:

#include <stdio.h>
#include <string.h>
size_t strlen = 10;
int main()
{
	printf("%d\n", strlen);
}

在这里插入图片描述但是C语言头文件中的库函数是非常多的,我们在编写大型项目的时候就难免可能会定义与库函数同名的变量,从而造成命名冲突,为了解决这个问题,C++引入了命名空间的概念
命名空间:在C/C++中,变量,函数和后面学到的类都是大量存在的,这些变量、函数和类的名称都存在于全局作用域中,可能会导致很多冲突。使用命名空间的目的是对标识符的名称进行本地化,以避免命名冲突或名字污染,其中定义命名空间的关键字是namespace。

2.命名空间的定义

定义命名空间很简单,只要使用namespace关键字,后面跟上命名空间名字,然后接一对{}即可,{}即为命名空间的成员
命名空间有如下特点:

  • 命名空间的名称是随意取得;
  • 命名空间中可以定义函数、变量、类型;
  • 命名空间可以嵌套
  • 同一个工程中允许存在多个相同名称的命名空间,编译器最后会将其合成到同一个命名空间中;
- 命名空间中可以定义函数、变量、类型;
- namespace N1
{
	//定义变量
	int strlen = 10;
	//定义类型
	typedef struct SLNode
	{
		int data;
		struct SLNode* next;
	}SLNode;
	//定义函数
	void Swap(int* p1, int* p2)
	{
		int tmp = *p1;
		*p1 = *p2;
		*p2 = tmp;
	}
}
  • 命名空间可以嵌套
> namespace N1
{
	int strlen = 10;

	//嵌套定义
	namespace N2
	{
		typedef struct SLNode
		{
			int data;
			struct SLNode* next;
		}SLNode;
	}

	//嵌套多层
	namespace N3
	{
		void Swap(int* p1, int* p2)
		{
			int tmp = *p1;
			*p1 = *p2;
			*p2 = tmp;
		}
	}
}
  • 同一个工程中允许存在多个名字相同的命名空间,编译器最后会将其合成到同一个命名空间中
    在这里插入图片描述在这里插入图片描述

3.命名空间的使用

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

  • 命名空间名称加作用域限定符;
  • 使用using将命名空间中某个成员引入;
  • 使用using namespace将命名空间名称引入,其中作用域限定符为:“::”
namespace N
{
	int a = 0;
	int b = 1;

	int Add(int left, int right)
	{
		return left + right;
	}

	struct Node
	{
		struct Node* next;
		int val;
	};
}

命名空间名称加作用域限定符

#include <iostream>
using namespace std;
int main()
{
	cout << N::a << endl;
	cout << N::Add(2, 3) << endl;
	return 0;
}

在这里插入图片描述使用 using 将命名空间中某个成员引入:

#include <iostream>
using namespace std;
using N::a;
using N::Add;
int main()
{
	cout << a << endl;
	cout << Add(2, 3) << endl;
	return 0;
}

在这里插入图片描述使用 using namespace 将命名空间名称引入:

#include <iostream>
using namespace std;
using namespace N;
int main()
{
	cout << a << endl;
	cout << Add(2, 3) << endl;
	return 0;
}

总的来说,我们想要使用命名空间中的变量,一共有两种方法:
一种是使用作用域限定符 ::,
另一种是引入命名空间,而引入命名空间又分为部分引入和全部引入。

嵌套定义的命名空间的使用:对于嵌套定义的命名空间,我们逐层使用作用域限定符即可,当然也可以通过逐层引入命名空间的方式使用:
在这里插入图片描述

4.注意事项

1.一个命名空间中定义了一个新的作用域,这个域叫做命名空间域,命名空间中是所有内容都局限于该命名空间中;
2.命名空间中定义的变量都是全局变量:如下图,命名空间N中成员变量a可以在函数test被访问,说明a的作用域是全局,所以a是全局变量;
在这里插入图片描述3、编译器查找变量的规则是:默认现在局部域中查找,如果找不到,再到全局域中去找,如果在全局域中也没找到该变量,就报错;而命名空间的作用是改变编译器查找变量的规则,让编译器先到局部域中查找,如果找不到,就直接到命名空间中去找,再找不到就报错

三、C++的输入输出

C++的输入输出语句如下:

#include<iostream>
// std是C++标准库的命名空间名,C++将标准库的定义实现都放到这个命名空间中
using namespace std; 
int main()
{
	int a = 0;
	cin >> a;  //console in
	cout << a << endl;  //console out endline
	return 0;
}

在这里插入图片描述

说明:
1、使用 cout 标准输出对象(控制台)和 cin 标准输入对象(键盘)时,必须包含 头文件
以及按命名空间使用方法使用 std。

2、 cout 和 cin 是全局的流对象,endl 是特殊的C++符号,表示换行输出,他们都包含在包含 头文件中。

3、<<是流插入运算符,>>是流提取运算符。

4、使用C++输入输出更方便,不需要像 printf/scanf 输入输出时那样,需要手动控制格式;C++的输入输出可以自动识别变量类型。

5、实际上 cout 和 cin 分别是 ostream 和 istream 类型的对象,>>和<<也涉及运算符重载等知识, 这些知识我们我们后续才会学习,所以我们这里只是简单学习他们的使用。

注意:早期标准库将所有功能在全局域中实现,声明在.h后缀的头文件中,使用时只需包含对应头文件即可,后来将其实现在std命名空间下,为了和C头文件区分,也为了正确使用命名空间, 规定C++头文件不带.h;旧编译器(vc 6.0)中还支持格式,后续编译器已不支持,因此推荐使用+std的方式。
在这里插入图片描述std命名空间的使用惯例:std是C++标准库的命名空间,如何展开std使用更合理呢?

1、 在日常练习中,建议直接using namespace std即可,因为这样很方便。

2、using namespace std展开后,标准库就全部暴露出来了,如果我们定义跟库重名的类型/对 象/函数,就存在冲突问题;

3、该问题在日常练习中很少出现,但是项目开发中代码较多、规模大,就很容易出现;所以建议在项目开发中像 std::cout 这样使用时指定命名空间 + using std::cout 来展开常用的库对象/类型等方式。

四、缺省参数

1.缺省参数概念

缺省参数是声明或定义函数时为函数指定一个缺省值;在调用函数时,如果没有指定实参则采用该形参的缺省值,否则使用指定的实参;
在这里插入图片描述

2.缺省参数的分类

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

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

半缺省参数:

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

注意事项:

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

  • 缺省参数不能在函数声明和定义中同时出现,如果既存在函数声明,又存在函数定义,那么缺省参数只能在函数声明处给定;
    在这里插入图片描述缺省值必须是常量或者全局变量。

五、函数重载

1.函数重载概念

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

#include<iostream>
using namespace std;

//参数类型不同构成函数重载
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;
}

int main()
{
	Add(10, 20);
	Add(10.1, 20.2);
	f();
	f(10);
	f(10, 'a');
	f('a', 10);
	return 0;
}

在这里插入图片描述

2.函数重载的原理(重要)

 我们在学习C语言时程序环境和预处理的时候知道了一个程序要运行起来的时候,需要经历预处理、编译、汇编、链接四个阶段;其中编译阶段会进行符号汇总,汇编阶段会生成符号表,而链接阶段则会对符号表进行合并与重定位,其中符号表会将每一个变量都关联上一个地址,但这个地址是否有效需要在链接阶段进行符号表的合并与重定位时才能检查出来;

 而对于上述过程中生成符号表这一阶段,C编译器与C++编译器所进行的操作是不同的 – C语言编译器会直接用变函数名作为符号表中的符号,而不会对函数名进行修饰;而C++编译器则是会对函数名进行修饰,用修饰后的名称来构成符号表。

注:由于Windows下vs的修饰规则过于复杂,而Linux下g++的修饰规则简单易懂,下面我们使 用了g++演示了这个修饰后的名字。

采用C语言编译器编译后结果:
在这里插入图片描述在这里插入图片描述经过对比后我们发现:在linux下,采用gcc (C语言编译器) 编译完成后,函数名字的修饰没有发生改变;而采用g++ (C++编译器) 编译完成后,函数名字的修饰发生改变,函数名由 前缀_Z+函数长度 +函数名+类型首字母 组成,即编译器将函数参数类型信息添加到了修改后的名字中。

通过上面这个实例就理解了:C语言没办法支持重载是因为同名函数没办法区分;而C++是通过函数修饰规则来区分,只要参数类型不同,修饰出来的名字就不一样,所以就支持重载。

同样,我们也理解了函数的返回值不同以及同类型参数的顺序不同是不构成重载的,因为C++编译器没办法区分;但其实即使是C++编译器把函数的返回值类型也加入了函数修饰规则,也仅仅是让它在语法层面是构成了重载而已,在实际使用中也是不构成重载的,因为函数传参时并不会传递函数的返回值类型,那么对于返回值不同,其他各方面都相同的函数而言,操作系统就不知道应该将参数传递给哪个函数,即在传递参数时出现了二义性,这时候编译也是会报错的。

注:对C/C++函数调用约定和名字修饰规则感兴趣的同学可以拓展学习一下下面这篇文章,里面有对vs下函数名修饰规则的讲解:C/C++ 函数调用约定

六、内联函数(重要)

1.内联函数的概念

函数栈帧的创建和销毁我们了解,一个函数在开始调用时会建立函数栈帧,结束时会销毁函数栈帧,而函数栈帧的建立与销毁是有空间和时间上的开销的;

那么,对于功能简单、调用次数非常多的函数来说,每次调用都重新开辟栈帧势必就会造成效率的降低;比如 hoare 法的快速排序中,我们仅仅是每次单趟排序都会调用很多次 Swap 函数,更别说单趟排序也会被递归调用很多次,而 Swap 函数本身的功能恰好十分简单,那么该如何来对其进行优化呢?

在C语言中,我们使用宏函数来解决这个问题:我们直接将 Swap 函数写成宏函数,这样使得程序在预处理阶段直接将 Swap 函数替换成相应的代码,从而不再建立函数栈帧。

//源代码
#include <stdio.h>
#define Add(x,y) ((x)+(y))  //宏函数
int main()
{
	int ret = Add(2, 3);
	printf("%d\n", ret);
}

//经过预处理之后的代码
{
    //...此处是 stdio.h 展开的内容
}
int main()
{
    int ret = ((2)+(3));
    printf("%d\n", ret);
}

但是宏有如下主要缺点:

    宏不能调试;
    宏没有类型安全检查;
    宏非常容易写错;

至于为什么宏有这些缺点以及这些缺点的具体体现场景,我在 程序环境和预处理 中已经有过介绍,这里就不再赘述。

基于C语言宏函数的这些缺陷,C++设计了内联函数:

以 inline 关键字修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开 (用函数体替换函数的调用),没有函数调用建立栈帧的开销,内联函数可以提升程序运行的效率;

内联函数的编写和正常函数一样,仅仅是在函数的返回值类型前添加一个 inline 关键字(这样就解决了C语言宏函数容易写错以及没有类型安全检查的缺陷);

同时,在 debug 模式下,内联函数不会自动展开,需要我们对编译器进行相关设置;在 release 模式下,内联函数会自动展开(这样解决了C语言宏函数无法调试的缺陷);

所以说:内联函数在继承了C语言宏函数优点的同时几乎避免了其所有的缺陷。

内联函数定义及查看

定义:

//普通函数
int Add(int x, int y)
{
	return x + y;
}

//内联函数--添加inline关键字
inline int Add(int x, int y)
{
	return x + y;
}

内联函数的查看
1、 在 release 模式下,编译器会自动将内联函数展开,但由于 release 模式无法调试,所以我们这里无法观察;

2、 在 debug 模式下,需要在 项目->属性 中对编译器进行如下设置,否则不会展开 (因为 debug 模式下,编译器默认不会对代码进行优化,以下给出 VS2019 的设置方式)
在这里插入图片描述在这里插入图片描述在完成上述设置后我们 F10 进入调试,然后单击右键转到反汇编查看汇编代码:

普通函数的汇编代码
在这里插入图片描述在这里插入图片描述
在这里插入图片描述内联函数的汇编代码
。。。写不完了

七、auto关键字

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值