C++------初见

本文介绍了C++语言的基础知识,包括关键字、命名空间的定义与使用,C++中的I/O操作,缺省参数的概念与分类,函数重载的原理,引用和内联函数的概念,C++11中的关键字和基于范围的循环,以及C++11引入的指针空值。重点讲述了命名空间如何解决命名冲突,以及C++11的新特性如何简化编程,如基于范围的循环和指针空值常量`nullptr`的使用。
摘要由CSDN通过智能技术生成

首先我们要了解为什么会有C++的出现?
之所以要有C++,是因为C语言存在一定的缺陷,为了弥补这些缺陷,才出现了C++,除此之外,在C++中还引入了新的语法特性。下面简单介绍一下:

1. C++关键字

学习一门新的语言,都要从一个一个字开始,所以我们先来了解一下C++中的关键字。
在C++98中,有63个关键字,如下表:
在这里插入图片描述
这里并不对这些关键字一一介绍,在日常的学习中会慢慢接触到。

2. 命名空间

在C/C++中,完成一个项目时,必定会有大量的变量、函数,而这些变量和函数都是全局变量,所以难免存在命名冲突,如果为了避免命名冲突就去把变量名(函数名)给的很复杂,就会给之后的维护带来很大的麻烦。
而命名空间的出现很好的解决了这个问题,它的关键字namespace,命名空间实际上就是一个新的作用域,以避免命名冲突或命名污染。

2.1 定义

在定义命名空间时,首先要有关键字namespace,后面跟你要对这段空间起的名字,然后是{}{}里面的内容就是命名空间的成员。示例:

  • 普通的命名空间
namespace  N1
{
	int a = 10;
	int add(int left, int right)
	{
		return left + right;
	}
}
  • 嵌套式命名空间
namespace N1
{
	int a = 20;
	int sub(int left, int right)
	{
		return left - right;
	}
	namespace N2
	{
		int a = 30;
		int mul(int left, int right)
		{
			return left * right;
		}
	}
}
  • 允许重名的命名空间
//当发生重名时,编译器会自动合成多个空间为一个
namespace  N1
{
	int mul(int left, int right)
	{
		return left * right;
	}
}

注:
一个命名空间在定义完一段作用域以后,这个命名空间中的所有内容都被局限于该命名空间之中。

2.2 使用

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

  • 使用命名空间名称及作用域限定符(推荐使用)
int main()
{
   printf("%d\n", N::a);
   return 0;
}
  • 使用using引入命名空间中的成员
using N::a;
int main()
{
	printf("%d\n", a);
	return 0;
}
  • 使用using namespace 命名空间名称引入(最不推荐使用,在预处理阶段会展开,增加代码量,降低效率)
using namespace N;
int main()
{
	printf("%d\n", a);
	printf("%d\n", b);
	add(10, 25);
	return 0;
}
3. C++中的I/O

在C语言中,有很多的输入/输出函数,常用的printfscanf,以及getsputs等等,但在C++中使用cincout,这两个函数相比C语言中的输入/输出函数更为简便,不需增加数据格式控制。例如:整形–%d,字符–%c。
示例:

#include<iostream>//头文件
using namespace std;//std--标准命名空间
int main()
{
	int a = 10;
	char b = 'b';
	double c;
	cin >> c;
	cout << a << " " << b << " " << c << endl;
	return 0;
}

显示结果:
在这里插入图片描述

注:
在使用cout标准输出(控制台)和cin标准输入(键盘)时,必须包含<iostream >头文件以及std标准命名空间。

4. 缺省参数
4.1 概念

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

void fun(int a = 1)
{
	cout<<a<<endl;
}
int main()
{
	fun();//不传参时打印1
	fun(10);//传参时打印10
	return 0;
}
4.2 分类

1).全缺省参数
示例:

void fun(int a=1,int b=2,int c=3)
{
	cout<<a<<endl;
	cout<<b<<endl;
	cout<<c<<endl;
}

2).半缺省参数
示例:

void fun(int a,int b=2,int c=3)
{
	cout<<a<<endl;
	cout<<b<<endl;
	cout<<c<<endl;
}

注:

  • 半缺省参数的参数必须从右往左依次给出,不能间隔给出;
    形参向实参传递时,从左往右依次传递 ;
  • 缺省参数不能在函数声明和定义中同时出现(如果同时出现,而恰巧声明和定义提供的值不同时,编译器会无法确定到底该用那个缺省值);
    例如:
    int fun(int a=10); //函数声明
    int fun(int a=10) {return a;} //函数定义
    缺省参数可以在声明中,也可以在定义中,但最好在声明中给出
  • 缺省值必须是常量或者全局变量 ;
  • C语言不支持(编译器不支持)。
5. 函数重载
5.1 概念

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

示例:

short add(short a, short b)
{
	return a + b;
}
int add(short a, short b)
{
	return a + b;
}

上例属于函数重载吗?
显然,它不属于函数重载 ,因为两个函数的参数列表完全相同,不同的只有返回值类型。

注:

  • 在C++中,有四种作用域,分别是全局、局部、类域、命名空间;
  • 函数重载中对返回值类型没有要求。
5.2 名字修饰

名字修饰实际上是一种在编译过程中,将函数、变量的名称重新改编的机制。简单来说就是编译器为了区分各个函数,将函数通过某种算法,重新修饰为全局中唯一存在的名称。
在C/C++中,一个程序要运行起来,需要经历以下几个阶段:预处理、编译、汇编、链接。

预处理:去注释、宏替换、包含头文件、处理预处理指令;
编译:把代码转换成汇编语言、词法分析、语法分析、语义分析、优化处理;
汇编:把汇编语言翻译成机器指令,生成目标文件;
链接:把所有的目标文件进行链接,生成可执行文件。

在这里插入图片描述

C语言的名字修饰规则非常简单,示例:
在这里插入图片描述
上述add函数只给了声明没有给定义,因此在链接时就会报错。

从报错结果中可以看出,C语言的名字修饰规则只是简单的在函数名前加下划线。因此当工程中存在相同函数名的函数时,就会产生冲突。

而C++因为要支持函数重载、命名空间等,所以其名字修饰规则相对复杂,不同编译器在底层的实现方式可能都不同(这里是VS2017)。
在这里插入图片描述
通过VS中显示的错误可以看出,编译器在底层使用的并不是 add 名字,而是被重新修饰过的一个比较复杂的名字,被重新修饰后的名字中包含了函数的名字以及参数类型。

这就是函数重载中几个同名函数要求其参数列表必须不同的原因:
只要参数列表不同,编译器在编译时通过对函数名字进行重新修饰,将参数类型包含在最终的名字中,就可以保证函数名在底层的全局唯一性。在这里插入图片描述

5.3 extern"C"

如果在C++的工程中,需要C风格的函数,只需要在函数名前加上extern"C"即可,这个关键字是告诉编译器将该函数按照C语言规则来编译。
在这里插入图片描述
此时,链接后就变成了C风格的命名修饰规则。

6.引用

由于引用的内容相对较多,也比较重要,为了读者有一个良好的阅读体验,此处将引用的知识点单独列出一篇博客:引用

7. 内联函数
7.1 概念

inline关键字修饰的函数叫做内联函数,在编译时C++编译器会在调用内联函数的地方展开,并没有函数压栈的开销,所以内联函数的程序运行的效率比较高。

这是没有inline关键字修饰的情况:

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

int main()
{
	int& ret = Add(1, 2);
	return 0;
}

查看反汇编:
在这里插入图片描述
这里明显可以看到调用add函数,这就势必要考虑函数压栈的开销,而这样就会降低效率。

然后再来看一下使用了inline关键字以后它的反汇编代码,要在VS2017中使用inline关键字,需要对编译器做一点修改。如下:
在这里插入图片描述
在这里插入图片描述
对编译器修改完以后(在完成了这次操作以后,记得将VS的设置修改回去),再次查看反汇编:
在这里插入图片描述
加了inline关键字以后,函数就会变成内联函数,在编译期间编译器会用函数体替换函数的调用。

7.2 特性
  • inline是一种以空间换时间的做法,省去调用函数的额外开销,所以当代码很长或者有循环/递归的函数不适宜使用内联函数;
  • inline对于编译器而言只是一个建议,编译器会自动优化,如果定义为inline的函数体内有循环/递归,编译器在优化时会忽略掉内联关键字;
  • inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开以后,就没有函数地址了,链接无法找到。
8. auto关键字(C++11)
8.1 auto简介

auto在之前的C/C++版本中是一个存储类型的指示符,但在(C++11) 中,它是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得。

int test()
{
	return 5;
}
int main()
{
	int a = 10;
	auto b = a;
	auto c = 'c';
	auto d = 1.2;
	auto e = test();
	auto e;
	return 0;
}

编译结果如下:
在这里插入图片描述
通过这个我们可以知道:在使用auto关键字定义变量时必须对其进行初始化,在编译阶段编译器需要根据初始化表达式来推导auto的实际类型。auto本身并不是一种“类型”,而是在类型声明时作为一个占位的作用(比如208中的“0”),编译器会在编译期间auto替换为变量实际的类型。

8.2 使用规则

1). auto与指针和引用一起使用
使用auto声明变量,声明指针类型是使用autoauto*没有区别,但声明引用类型时,必须加上&

示例:

int main()
{
	int a = 10;
	auto&c = a;
	auto d= 'a';
	auto *e = &d;
	cout << typeid(a).name() << endl;
	cout << typeid(c).name() << endl;
	cout << typeid(d).name() << endl;
	cout << typeid(e).name() << endl;

	return 0;
}

结果如下:
在这里插入图片描述
2). 在同一行定义多个变量
当想在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器会报错。实际上编译器只对第一个变量的类型进行推导,然后用推导出来的类型定义同一行的其他变量。

示例:

int main()
{
	int a = 10;
	auto b = a, c=1.3;
	auto d = 10, e = 20;
	return 0;
}

编译结果如下:

在这里插入图片描述
当想在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器会报错。实际上编译器只对第一个变量的类型进行推导,然后用推导出来的类型定义同一行的其他变量。

8.3 不能使用的场景

1). auto不能作为函数的参数( 因为编译器无法对a的实际类型进行推导 )
2). auto不能直接用来声明数组
3). 为了避免与C++98中的auto发生混淆,C++11中只保留了auto作为类型指示符的用法
4). auto在实际中常见的优势用法是和C++11中提供的基于范围的for循环,lambda表达式等进行配合使用
5). auto不能定义类的非静态成员变量
6). 实例化模板时不能使用auto作为模板参数

9. 基于范围的for循环(C++11)
9.1 语法

先来看一下C++98中是怎样来实现for循环的?

int main()
{
   int array[] = { 1,2,3,4,5,6 };
   for (int i = 0; i < sizeof(array) / sizeof(int); i++)
   	cout << i << endl;
   return 0;
}

而在这段代码中,我们花了很多时间去算数组的大小。事实上,对于一个有范围的集合而言,由程序员自己来说明循环的范围是多余的,有时候还会出错。因此C++11中引入了基于范围的for循环。

for循环后的括号由冒号“ :”分为两部分:
第一部分是范围内用于迭代的变量, 第二部分则表示被迭代的范围。

示例:

int main()
{
   int array[] = { 1,2,3,4,5,6 };
   for (auto& e : array)
   	cout << e << endl;
   return 0;
}

使用C++11中的循环方法后,可以很明显的发现代码量变少了很多。

9.2 使用条件

1).for循环迭代的范围必须是确定的:
对于数组而言,就是数组中第一个元素到最后一个元素的范围;
对于类而言,应该提供得到 beginend的 方法,beginend就是for循环迭代的范围。
2). 迭代的对象要实现++和==的操作。

10. 指针空值(C++11)
10.1 C++98中的指针空值

作为一个优秀的程序猿,声明一个变量给该变量一个合适的初始值是很有必要的,否则可能会出现难以预计的错误,比如未初始化的指针。C++98中我们经常使用NULL来完成这个任务,而NULL实际是一个宏,不信你看:

int main()
{
	int *p = NULL;
	return 0;
}

NULL转到定义发现:
在这里插入图片描述
可以看到,NULL要么被定义为字面常量0,要么被定义为无类型的指针(void*)的常量。不论使用哪一种定义,在使用空值的指针时,都不可避免的会遇到一些麻烦,比如:

void test(int)
{
   cout << test() << endl;
}
void test(int *)
{
   cout << test(int *) << endl;
}
int main()
{
   test(0);
   test(NULL);
   test((int *)NULL);
   return 0;
}

编译结果如下:
在这里插入图片描述
可以看到,不但有一些调用过程中出现的错误,还有一些语法错误,而事实上这些语法错误是不存在的。

在C++98中,字面常量0既可以是一个整形数字,也可以是无类型的指针(void*)常量,但是编译器默认将其看成是一个整形常量,如果要将其按照指针方式来使用,则必须对其进行强转(void *) 0

10.2 nullptrnullptr_t

为了考虑兼容性,C++11并没有消除常量0的二义性,而是给出了全新的nullptr表示空值指针。
C++11之所以不在NULL的基础上进行扩展,是因为NULL本身是一个宏,而且不同的编译器对于NULL的实现可能不同,如果直接扩展NULL,可能会影响以前的程序。

故此:为了避免混淆,C++11提供了nullptr,即nullptr代表一个指针空值常量。nullptr是有类型的,其类型为nullptr_t,仅仅可以被隐式转化为指针类型,nullptr_t被定义在头文件:

typedef decltype(nullptr) nullptr_t;

注意:
1). 在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为新关键字引入的。
2). 在C++11中,sizeof(nullptr)sizeof((void*)0)所占的字节数相同(都占4个字节)。
3). 为了提高代码的健壮性,在后续表示指针空值时建议好使用nullptr

以上是对C++的一些简单说明,C++天下第一,它的深度远远不是一篇博客就可以得出的,希望大家多读书多看报,少刷网剧多刷代码!!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值