C++入门(笔记)

前言

本系列博客旨在记录学习C++时的一些点以及在学习上遇到的一些困难,所以更倾向于学习笔记,,更偏向于理解,因为每个人的记录风格不同嘛,所以我就以我自己喜欢的方式展现给大家了,那么现在就开始学习C++的旅途吧!

关于C++的相关介绍

什么是C++

C++(c plus plus)是一种计算机高级程序设计语言,由C语言扩展升级而产生 ,最早于1979年由本贾尼·斯特劳斯特卢普在AT&T贝尔工作室研发。 

C++既可以进行C语言的过程化程序设计,又可以进行以抽象数据类型为特点的基于对象的程序设计,还可以进行以继承和多态为特点的面向对象的程序设计。C++擅长面向对象程序设计的同时,还可以进行基于过程的程序设计。 C++几乎可以创建任何类型的程序:游戏、设备驱动程序HPC桌面嵌入式移动应用等。 甚至用于其他编程语言的库和编译器也使用C++编写。 

C++拥有计算机运行的实用性特征,同时还致力于提高大规模程序的编程质量与程序设计语言的问题描述能力。 

C++的发展历程

1970年,AT&T贝尔实验室的工作人员D.Ritchie和K.Thompson共同研发了C语言。研制C语言的初衷是用它编写UNIX系统程序,因此,实际上C语言是UNIX的“副产品”。

1971年,瑞士联邦技术学院N.Wirth教授发明了第一个结构化的编程语言Pascal

20世纪70年代中期,本贾尼·斯特劳斯特卢普在剑桥大学计算机中心工作。斯特劳斯特卢普希望开发一个既要编程简单、正确可靠,又要运行高效、可移植的计算机程序设计语言。而以C语言为背景,以Simula思想为基础的语言,正好符合斯特劳斯特卢普的初衷和设想。

1979年,本贾尼·斯特劳斯特卢普到了AT&T贝尔实验室,开始从事将C改良为带类的C(C with classes)的工作。、1983年,该语言被正式命名为C++。 

1985年、1990年和1994年,C++先后进行3次主要修订。

C++的标准化工作于1989年开始 [21],并成立了一个ANSI和ISO(International Standards Organization)国际标准化组织的联合标准化委员会。

1994年1月25曰,联合标准化委员会提出了第一个标准化草案。在该草案中,委员会在保持斯特劳斯特卢普最初定义的所有特征的同时,还增加了部分新特征。 

在完成C++标准化的第一个草案后不久,亚历山大·斯特潘诺夫(Alexander Stepanov)创建了标准模板库(Standard Template Library,STL)。在通过了标准化第一个草案之后,联合标准化委员会投票并通过了将STL包含到C++标准中的提议。STL对C++的扩展超出了C++的最初定义范围。虽然在标准中增加STL是个很重要的决定,但也因此延缓了C++标准化的进程。

1997年11月14日,联合标准化委员会通过了该标准的最终草案。

1998年,C++的ANSI/IS0标准被投入使用。

语言标准

自C++的ANSI/IS0标准投用以来,共进行过5次更新。

C++语言标准更新记录

标准版本

发布时间

正式名称

更新内容

C++ 03

2003年

ISO/IEC 14882:2003

对C++ 98版本的漏洞做了部分修改。 

C++ 11

2011年8月12日

ISO/IEC 14882:2011

对容器类的方法做了三项主要修改:

1、新增了右值引用,可以给容器提供移动语义。

2、新增了模板类initilizer_list,因此可将initilizer_list作为参数的构造函数和赋值运算符。

3、新增了可变参数模板(variadic template)和函数参数包(parameter pack),可以提供就地创建(emplacement)方法。 

C++ 14

2014年8月18日

ISO/IEC 14882:2014

C++11的增量更新。主要是支持普通函数的返回类型推演,泛型lambda,扩展的lambda捕获,对constexpr函数限制的修订,constexpr变量模板化等。 

C++ 17

2017年12月6日

ISO/IEC 14882:2017

新增UTF-8 字符文字、折叠表达式(fold expressions):用于可变的模板、内联变量(inline variables):允许在头文件中定义变量;在if和switch语句内可以初始化变量;结构化绑定(Structured Binding):for(auto [key,value] : my_map){…};类模板参数规约(Class Template Argument Deduction):用pair p{1, 2.0}; 替代pair{1, 2.0};;>;static_assert的文本信息可选;删除trigraphs;在模板参数中允许使用typename(作为替代类);来自 braced-init-list 的新规则用于自动推导;嵌套命名空间的定义;允许命名空间和枚举器的属性;新的标准属性:[[fallthrough]], [[maybe_unused]] 和 [[nodiscard]];对所有非类型模板参数进行常量评估;Fold表达式,用于可变的模板;A compile-time static if with the form if constexpr(expression);结构化的绑定声明,允许auto [a, b]=getTwoReturnValues()。 

C++ 20

2020年12月7日

ISO/IEC 14882:2020

新增模块(Modules)、协程(Coroutines)、范围 (Ranges)、概念与约束 (Constraints and concepts)、指定初始化 (designated initializers)、操作符“<=> != ==”;constexpr支持:new/delete、dynamic_cast、try/catch、虚拟、constexpr向量和字符串;计时:日历、时区支持。

语言特点 

  • 与C语言的兼容性

C++与C语言完全兼容,C语言的绝大部分内容可以直接用于C++的程序设计,用C语言编写的程序可以不加修改地用于C++。

  • 数据封装和数据隐藏

在C++中,类是支持数据封装的工具,对象则是数据封装的实现。C++通过建立用户定义类支持数据封装和数据隐藏。

在面向对象的程序设计中,将数据和对该数据进行合法操作的函数封装在一起作为一个类的定义。对象被说明为具有一个给定类的变量。每个给定类的对象包含这个类所规定的若干私有成员、公有成员及保护成员。完好定义的类一旦建立,就可看成完全封装的实体,可以作为一个整体单元使用。类的实际内部工作隐藏起来,使用完好定义的类的用户不需要知道类的工作原理,只要知道如何使用它即可。

  • 支持继承和重用

在C++现有类的基础上可以声明新类型,这就是继承和重用的思想。通过继承和重用可以更有效地组织程序结构,明确类间关系,并且充分利用已有的类来完成更复杂、深入的开发。新定义的类为子类,成为派生类。它可以从父类那里继承所有非私有的属性和方法,作为自己的成员。

  • 多态性

采用多态性为每个类指定表现行为。多态性形成由父类和它们的子类组成的一个树型结构。在这个树中的每个子类可以接收一个或多个具有相同名字的消息。当一个消息被这个树中一个类的一个对象接收时,这个对象动态地决定给予子类对象的消息的某种用法。多态性的这一特性允许使用高级抽象。

继承性和多态性的组合,可以轻易地生成一系列虽然类似但独一无二的对象。由于继承性,这些对象共享许多相似的特征。由于多态性,一个对象可有独特的表现方式,而另一个对象有另一种表现方式。

C++中的关键字

ISO C++ 98关键字共63个,按标准排版如下:

ISO C++ 98关键字表

asm

do

if

return

typedef

auto

double

inline

short

typeid

bool

dynamic_cast

int

signed

typename

break

else

long

sizeof

union

case

enum

mutable

static

unsigned

catch

explicit

namespace

static_cast

using

char

export

new

struct

virtual

class

extern

operator

switch

void

const

false

private

template

volatile

const_cast

float

protected

this

wchar_t

continue

for

public

throw

while

default

friend

register

true

delete

goto

reinterpret_cast

try

语言评价 

C++语言是对C语言的扩充,从Simula中吸取了类,从ALGOL语言中吸取了运算符的一名多用、引用和在分程序中任何位置均可说明变量,综合了Ada语言的类属和Clu语言的模块特点,形成了抽象类,从Ada Clu和ML等语言吸取了异常处理,从BCPL语言中吸取了用//表示注释C++语言保持了C语言的紧凑灵活、高效以及易于移植性强等优点,它对数据抽象的支持主要在于类概念和机制,对面向对象风范的支持主要通过虚拟机制函数因C++语言既有数据抽象和面向对象能力,运行性能高,加上C语言的普及,而从C语言到C++语言的过渡较为平滑,以及C++语言与C语言的兼容程度可使数量巨大的C语言程序能方便地在C++语言环境中复用,使C++语言在短短几年内能流行。(计算机软件专家王汝传 评) 

作为最受欢迎的编程语言之一,C++带给开发者们最大的惊喜便是其强大的特性。一方面跟C兼容,可以直面系统底层API,SDK,另一方面提供了很多范式,足够的抽象能力,面向对象,操作符重载,模板等。(脚本之家 评)

以上是有关C++的介绍内容,下面就到了相关知识点介绍了。由于之前的博客中介绍了命名空间的相关概念及其用法这里就不多赘述了,具体可以看看我写的这篇博客:C语言基础&&数据结构&&笔记-CSDN博客

这里我们来说说C++中的输入与输出是怎么实现的,虽然这个我在之前也提到过但一直是粗略的讲了一下,这里我们就详细的讲一下。

一、C++的输入与输出

1.1 输出

在C++中,我们要想在控制台上像C语言中的printf()函数一样进行输出打印,就得用到:

cout

 注意是cout,不是count,我刚开始记的时候就记成count了,count在英文是代表计数的意思,这里是输出,跟计数肯定没什么关系,所以这么记就行了,然后就是📌具体的操作为:cout<< 想要输出的内容

下面就举个代码例子以方便大家理解

注意:首先我们在使用输入关键字cout时必须要有头文件#include <iostream>,然后正确的按命名空间的方法使用std(具体的使用方法可以看一下之前的博客内容,这里我就按我的看法解释一下吧,就是你想使用这个cout关键字你得找有这个关键字的头文件要,类比printf()使用前要先有#include <stdio.h>,但这里光找到头文件还不行,这个关键字还被一个密码箱锁着的,这里的命名空间用法就是将std有关的内容公开,这样才能使用),其次这里的“<<”为流插入运算符。

注意;这里的endl起到了换行的作用,类比于\n,由上图也可直到cout可以自动识别类型。(如整型,浮点型,甚至是字符串等等)

1.2 输入

在C++中的输入与C语言的scanf()函数不同,这里用到了:

cin

📌具体操作:cin>>想要输入的内容 

注意 :在使用cin这个关键字之前一定要有头文件#include <iostream>,然后正确的按命名空间的方法使用std,其次这里的“>>”为流插入运算符。

下面进行应用举例:

由上图也可直到cin可以自动识别类型。(如整型,浮点型,甚至是字符串等等) 

二、缺省参数

2.1 缺省参数是什么

📌缺省参数 也被叫做默认参数,是声明或定义函数时为函数的参数指定一个缺省值(或者叫默认值)。在调用该函数时,如果没有指定实参则采用该形参的缺省值,否则使用指定的实参。(注意,给的缺省值必须是一个常量、全局变量或者静态变量)

举个例子:

看着是不是很神奇? 

2.2 全缺省参数、半缺省参数

下面来进行实例展示:

#include <iostream>
using namespace std;//注意这行代码
int Add(int a = 1, int b = 2, int c = 4);

int main()
{
	cout<<Add()<< endl;//没有传参时,使用默认值
	cout<<Add(6)<<endl;//有参数传入,使用指定的参数
	cout<<Add(6, 6)<<endl;//有参数传入,使用指定的参数
	cout<<Add(6, 6, 6)<<endl;//有参数传入,使用指定的参数
}

int Add(int a, int b, int c)
{//定义一个简单的加法函数
	int sum;
	sum = a + b + c;
	return sum;
}

首先可以看到,随着我们每次传入的参数不同,打印出来的值也各种各样,再就是我在打这段代码时遇到的一些问题我也写一下吧,毕竟这算是学习笔记,有不懂的或者错误就得记下来,虽然上面的代码很简单啦,首先我在使用ADD函数的时候将被调用的函数放在了主函数的下方,注意我此时的函数定义参数是有缺省参数的,后面我就在上方加了一个函数声明,以为这样就可以运行了,结果就引出缺省参数使用的一个问题,在函数定义和函数申明中,缺省参数不能同时出现(如果同时出现,假如申明中的参数与定义中的不同,机器会不知道使用哪个),当然后面改过来了,不然就不会有这个代码的运行结果图的啦~

根据上述的代码运行结果图不能发现,可以对部分参数进行传参也可以对所有的参数进行传参。(注意:传参是按照相应的顺序进行的,不能中间杠一个进行传,就是不能是那种传a、c不传b,这种情况是不允许的,定义缺省参数的时候同样也是,不能中间缺一段,而是按顺序依次定义)

三、函数重载

大家都应该知道一词多义吧,人们可以在不同的语境与氛围下对同一个词或者句子有不同的解释,这就是重载,就比如“我醉了”,在饭局上这么说可能就是真的喝醉了,但平常情况,又不喝酒的时候说就可能就是表示无奈的一种感叹。

3.1 函数重载的概念

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

下面就举例说明:

#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;//注意这行代码
int Add(int a, int b, int c);
double Add(double a, double b, double c);
void text(int a);
void text();
void print(int a, float b);
void print(float b, int a);

int main()
{
	Add(10, 10, 10);
	Add(10.2, 10.4, 10.4);
	text(5);
	text();
	print(1, 0.2);
	print(0.2, 1);
}

//参数类型的不同
int Add(int a, int b, int c)
{
	cout << "int Add(int a, int b, int c)" << endl;
	return a + b + c;
}

double Add(double a, double b, double c)
{
	cout << "double Add(double a, double b, double c)" << endl;
	return a + b + c;
}

//参数的个数不同
void text(int a)
{
	cout << "text(a)" << endl;
}


void text()
{
	cout << "text()" << endl;
}

//参数的顺序不同
void print(int a, float b)
{
	cout << "print(int a, float b)" << endl;
}

void print(float b, int a)
{
	cout << "print(int a, float b)" << endl;
}

可以看到上面的代码定义了六个函数,三个函数名,作用域中如此定义C++是支持的,上方的定义可以看到两个Add函数的参数类型不一样,两个text函数的参数的个数不一样,两个print函数的参数的书写顺序不一样,在C++中这些都可以存在,而且完全不影响,C++编译器编译时可以根据所识别到的各个函数的参数类型来进行识别,区分每个函数。

运行结果如下:

 3.2 函数重载的原理

这里因为自己的一些技术限制的原因,我就借鉴一下前辈的博客来帮助理解一下:【C++】入门(上)-CSDN博客

通俗的一点说就是C++能支持函数重载的原因是因为它比起C语言的识别多了个函数参数类型的识别,当然具体原理可以看看上方的博客,我也就是简单的口语总结而已啦。

四、引用

4.1 引用的概念

引用不是去新定义一个变量,而是给变量去一个别名,就好比一个人不仅有自己的名字,还有自己的绰号呀、小名、笔名等等,这些都是指同一个事物,表达的是同一个概念,所以系统不会给引用的变量开辟内存空间,引用的变量与当前被引用的变量共用一个空间。

具体操作:类型 & 引用变量名(对象名) = 引用实体。

下面用一段代码来举例:

#include <iostream>
using namespace std;
int main()
{
	//给a取三个不同的名字
	int a = 5;
	int& b = a;
	int& c = a;
	int& d = b;
	//看看他们各自的地址情况
	cout << &a << endl;
	cout << &b << endl;
	cout << &c << endl;
	cout << &d << endl;
    return0;
}

 让我们看一下代码的运行结果:

 注意:这里的引用类型与实体的类型一定要保持一致,其次我们可以从上述的运算结果可以看出,一个实体可以有多个引用,这点可以类比我们人的名字也可以有很多不同的称呼方法。

4.2 引用的特性列举

下面我们看个例子:

#include <iostream>
using namespace std;
int main()
{

	int a = 5;
	int& b = a;
	//这里给b进行重新的赋值
	int x = 10;
	b = x;
	cout << &a << endl;
	cout << &b << endl;
	return 0;
}

可以看到,给b重新赋值以后,b的地址依然不变,由此可以知道:引用一旦引用了一个实体之后就不会再引用其他实体了。

4.3 引用的使用背景

   4.3.1 输出型参数(形参影响实参的情况)

C语言在函数传参的时候,想要形参可以改变实参,就得使用指针,以及*的解引用,而在C++中不用这样,可以更加的方便快捷。

拿交换函数为例;

#include <iostream>
using namespace std;
void swap1(int* a, int* b);
void swap2(int& a, int& b);
int main()
{

	int a = 5;
	int b = 10;
	swap1(&a, &b);
	cout << a << endl;
	cout << b << endl;
	swap2(a, b);
	cout << a << endl;
	cout << b << endl;
	return 0;
}

void swap1(int* a, int* b)
{
	int temp = *a;
	*a = *b;
	*b = temp;
}

void swap2(int& a, int& b)
{
	int temp;
	temp=a;
	a = b;
	b = temp;
}

运行结果:

 可以发现,引用比指针要简单一些,直接起个别名然后直接用就行,不用像指针一样需要用到解引用的操作。

    4.3.2 引用返回

下面再让我们接着看: 

int text1()
{
	static int a = 0;
	return a;
}

int& text2()
{
	static int a = 0;
	return a;
}

上述的代码的唯一的区别是返回的值不同,那么到底区别在哪呢,这里由于我对函数栈帧还不太了解,就用我自己的类比来说明了,首先我们正常情况下返回值,要先开辟一个临时的一个空间存放返回的值,再将值取出返回,但这样很明显是有点多余了对吧,本着能省则省的心理,大家肯定都想一步到位对吧,所以这里的引用就做到了,它在返回值的时候,没有开辟一个临时空间,而是直接返回,就相当你过安检的时候,要把东西什么的放到传送带上检查才能拿走,而引用可以跳过这个步骤直接把东西拿着就走的那种,这样不仅减少空间上的浪费,也提高效率。具体的原理肯定不是我将的几句话这么简单啦。

注意:传的变量一定是出了传引用函数的函数栈帧时还未销毁的变量,不然会造成非法访问,人家都没了,还用它的别名到处使用想想就很怪吧。

    4.3.3 调用者可以修改返回对象

下面我们看一段操作:

#include <iostream>
#include <assert.h>
#define N 5
using namespace std;
typedef struct List
{//静态顺序表
	int a[N];
	int size;
}AY;
int& postAY(AY& ay, int x);

int main()
{
	AY arr;
	for (int i = 0; i < N; i++)
	{
		postAY(arr, i) = i;
	}
	for (int i = 0; i < N; i++)
	{
		cout<<postAY(arr, i)<<endl;
	}
	return 0;
}


int& postAY(AY& ay, int x)
{//该函数可以返回顺序表中的数组的第x个元素
	assert(x< N);
	return ay.a[x];
}

再来看一下运行结果:

可以看到arr数组每个都被赋上了相应的值,那我们接下来就就解释一下这两个for循环分别有什么作用:

第一个for循环:将每次返回的postAY所返回的顺序表引用的数组元素进行赋值;

第二个for循环:将每次返回的postAY所返回的顺序表引用的数组元素进行打印;

从这个例子可以看出调用者在引用返回时,可以修改返回对象。 

4.3.4 常引用

引用也可以用const来进行修饰。

注意:引用和指针一样只能进行权限的缩小或者保持,不能进行扩大。

下面就是一个错误的实例:

这个例子告诉我们,一个人或事物的本质不会因为他名字的改变而改变,我本来就是const型,只能读不能写,你换了一个名字后就能变得既能读又能写了吗,显然有问题! 

     4.3.4.1 权限保持

如下方的例子,看到a,b,p,p1都是const类型,类型没有发生改变,这个就是权限保持。

//权限保持
	const int a = 10;
	const int& b = a;
	const int* p = NULL;
	const int* pl = p;
     4.3.4.2 权限缩小 

如下方的例子,可以看到a,p为可读可写的整型变量,而b和p1为只读不可写的量,所以这里的权限变小了。

//权限缩小
	int a = 10;
	const int& b = a;
	int* p = NULL;
	const int* pl = p;
     4.3.4.3 常引用的其他用法 

先举个例子:

这个错误不难理解,通俗一点的说就是指鹿为马,明明人家是鹿,你偏偏给它起名叫马,这不是瞎扯嘛对吧,所以肯定会报错,那我们再看下面的例子。

是不是会很奇怪,这里为什么能运行呢?

这里我们首先要知道,系统开辟了一个临时空间用来存放int a转换成double类型的值,放好之后再给b去引用,所以这里b不是直接引用a而是引用的是这个临时空间。由于临时空间为常量类型,所以用到const来修饰b) 

4.4 引用与指针的区别

引用从语法上用即给其他变量去别名是不用开辟新的空间的,但是指针存储地址需要开辟新的空间,但是从底层汇编语言来说引用其实也开辟了空间

我们可以看到,在实现b与*p的汇编语言时都存储了a的地址,引用的基础底层还是用指针实现的。 

下面我们就仔细看一下指针和引用的一些区别:

1. 引用概念上定义一个变量的别名,指针存储一个变量地址。

2. 引用在定义时必须初始化,指针没有要求

3. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体

4. 没有NULL引用,但有NULL指针

5. 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节)

6. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小

7. 有多级指针,但是没有多级引用

8. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理

9. 引用比指针使用起来相对更安全


原文链接:https://blog.csdn.net/m0_70811813/article/details/128850339

此处引用了这位大佬的博客

五、内联函数 

C语言中的#define宏定义大家应该都很清楚,通俗一点就是给一个数据起个别名,就比如π,这种做法可以提高我们的运行效率,但这些基本都是将一些数字宏定义,在C++我们可以对整个函数进行宏定义,这种函数就叫做内联函数。

5.1 内联函数的使用方法

具体操作:普通函数的前面加个inline关键字

如果在普通函数前加个inline关键字将其改为内联函数,在编译期间编译器就会将函数的调用替换成函数体。(减少了函数栈帧的使用)

下面举例来说明:

int add(int a, int b)
{//普通函数
	return a + b;
}

inline int ADD(int a, int b)
{//内联函数
	return a + b;
}

我们来看一下函数栈帧的使用情况:

查看方式: 1. 在release模式下,查看编译器生成的汇编代码中是否存在call Add 2. 在debug模式下,需要对编译器进行设置,否则不会展开(因为debug模式下,编译器默认不 会对代码进行优化,以下给出vs2022的设置方式)

 

完成以上的两步的修改后就可以开始调试了,让我们看看调试的结果; 

 

我们可以发现在add的函数栈帧中是右call的,但在ADD的函数栈帧中是没有的。

5.2 内联函数的特性 

📌1. inline是一种以空间换时间的做法(编译后的.exe运行文件指令会增多),如果编译器将函数当成内联函数处理,在编译阶段,会用函数体替换函数调用,缺陷:可能会使目标文件变大,优势:少了调用开销,提高程序运行效率。

📌2. inline对于编译器而言只是一个建议,不同编译器关于inline实现机制可能不同,一般建议:将函数规模较小(即函数不是很长,具体没有准确的说法,取决于编译器内部实现)、不是递归、且频繁调用的函数采用inline修饰,否则编译器会忽略inline特性。

📌3. inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址 了,链接就会找不到。

以上的内容是引用的,通俗一点来说就是inline是一个高级的宏定义,可以对函数进行宏定义,我们在跑程序时,对于普通的函数我们需要调用,就是在使用时会跳转到相应的函数地址,然后对地址里面的程序进行使用,但对于inline来说我们在跑程序时碰到内联函数时,编译器会将该内联函数直接替换成函数体,相较于普通函数,省去了调用跳转的时间,所以程序的运行效率会提高,但是对于普通函数,只需要开辟一个空间存函数就行,但对于内联函数,你每使用一次就要开辟一次对应函数的空间,如果这个函数很短并且没有递归等操作还好,如果这个函数很大,调用频繁,甚至用到递归,然后你运行,那恭喜你,你可以换个电脑了,因为原来的内存已经炸了。


六、关键字auto

我们在定义变量类型时,除了自己定义之外,还可以交给auto关键字来解决。

具体操作:auto 变量名 = 初始化数据

下面我们就举个例子来分析一下:

#include <stdio.h>
#include <iostream>
using namespace std;
int main()
{
	int s=0;
	float f = 3.14;
	auto a = s;
	auto t = f;
	auto p = &s;
	cout << "a的类型为:" << typeid(a).name() << endl;
	cout << "t的类型为:" << typeid(t).name() << endl;
	cout << "p的类型为:" << typeid(p).name() << endl;
	return 0;
}

注意:typeid( ).name() 可以打印typeid的( )中的变量类型 。

我们可以看到,auto这个关键字可以自动识别a、t、p的类型为int、float和int*型,很好的将变量进行了初始化。 

但是在我们看来int、double这些类型用auto来替代并没有什么实际意义,但是在后期我们在使用C++中的一些变量名时将会非常长,这时用auto自动识别类型就显得方便很多了。

此外要注意的是auto识别指针变量的时候,下面举个例子:

    int a;
    auto p1 = &a;
    auto* p2 = &a;
    cout << "p1的类型为:" << typeid(p1).name() << endl;
    cout << "p2的类型为:" << typeid(p2).name() << endl;

运行结果如下:

 

可以看到p1、p2都是int*类型,说明在初始化时,auto与auto*的效果是一样的,此外用auto声明引用类型的时候必须要用&。 

另外再介绍几个auto不能使用的场景:

1.auto不能直接作为函数的参数:

2.auto不能用来直接声明数组:

 

3.auto在一行识别多个变量的时候,应该保持一致:

 

七、基于范围的for循环

正常C语言我们遍历数组时代码是这样写的:

    int arry[] = { 0 ,1,2,3,4,5,6,7 };
    for (int i = 0; i < sizeof(arry) / sizeof(int); i++)
    {
        printf("%d ", arry[i]);
    }

而对于C++我们可以用基于范围的for循环来实现: 

下面我们举个简单的例子:

    int arry[] = { 0 ,1,2,3,4,5,6,7 };
    for (auto a:arry)
    {
        cout << a << " ";
    }
    cout << endl;

运行结果如下: 

 

这个for循环的作用是自动获取数组arry的值并将其赋予可自动识别的变量a,并且自动判定结束。 

其中变量的类型不一定只是auto,可以是int类型或者是其他的类型等等,包括变量名也是,不一定只是a,具体怎样是由自己来决定,自己想怎么命名想使用什么类型都是任意的。就比如这里的int类型的数组我们就可以拿int类型的变量来接收:

int main()
{
    int arry[] = { 0 ,1,2,3,4,5,6,7 };
    for (int b : arry)
    {
        cout << b << " ";
    }
    cout << endl;
}

运行结果如下:

 

但是在实际使用中还是推荐去使用auto类型,因为可以接受任何类型的数组。

现在我们再做一些改变试试看:

int main()
{
    int arry[] = { 0 ,1,2,3,4,5,6,7 };
    for (int b : arry)
    {
        b *= 3;
        cout << b << " ";
    }
    cout << endl;
    for (int b : arry)
    {
        cout << b << " ";
    }
}

运行结果如下:

 

这里我们发现,我们对b进行修改,但对arry这个数组没有影响,原因是什么? 

这里的原理在于,b为单独的变量,与数组arry根本不是一个空间,所以改变变量b是无法直接改变数组arry的。如果我们想对这里的数组进行修改,我们可以考虑用引用来解决:

    for (int &b : arry)
    {
        b *= 3;
        cout << b << " ";
    }

运行结果如下:

这里的b是对数组arry元素的引用,对b进行改变就相当于对数组本身进行改变。

八、指针空值nullptr

在C语言中我们是这样将指针置空的:

int* p = NULL;

但在C++中我可以这样:

int* p = nullptr;

虽然这两个操作都是将指针置空的操作,那这两个操作有什么区别呢?

我们可以拿上面的重载函数来举例子,现学现卖,照葫芦画瓢嘛:

void a(int p)
{
    cout << "a(int)" << endl;
}
void a(int* p)
{
    cout << "a(int*)" << endl;
}
int main()
{
    a(0);
    a(NULL);
    return 0;
}

按照常理来说,第一条语句是调用第一个函数打印a(int),第二条语句是调用第二个函数打印a(int*) ,但实际上是怎么样的呢?我们来看看:

可以看到这里打印了两次a(int),相当于这里的NULL被识别成了int类型了 ,至于为什么,其中的原理是:C++中NULL实际是一个宏,相当于整型0,所以为了解决这个局限性,C++11语法就引入了关键字:nullptr。

让我们看看这个关键字的的实现:

❗下面要注意三点:

1. 在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为新关键字引入的。

2. 在C++11中,sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同。

3. 为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr。

算法题练习:

判断传入的字符串是否为有效括号的算法,这题我是和在朋友的耐心指点下完成的。

#include <stdio.h>
#include <string.h>
bool isVa(char* s) {//判断是否是有效括号的习题
    int top=0;
    char temp;
    char stack[10000];//可以用栈来实现判断操作
    for (int i = 0; i < strlen(s); i++)
    {
        if (s[i] == '[' || s[i] == '{' || s[i] == '(')
            stack[top++] = s[i];//只要当前传入的括号为左括号,就将其压入栈中
        else
        {//如果传入的为任意的右括号
            if (top == 0)
            {//如果当前栈为空,且传入的是右括号,则直接返回false
                return false;
            }
            temp = stack[--top];//若当前栈非空且传入的是右括号,则返回当前栈顶的元素与当前的右括号进行配对比较,如果不同则返回false
            if (!(temp == '[' && s[i] == ']'))
                return false;
            if (!(temp == '(' && s[i] == ')'))
                return false;
            if (!(temp == '{' && s[i] == '}'))
                return false;
        }
    }
    if (top == 0)//所有操作都解释以后若栈为空,则表示传入的为合法括号,否则非法
        return true;
    else
        return false;
}

本期博客结束,欢迎大家批评指正,下期再见。 

  • 17
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值