C++primeplus复习p92-p136

1.自动存储,静态存储,动态存储

(1)自动存储
在函数内部定义的变量使用自动存储空间,被称为自动变量(也叫局部变量),为什么说自动,也就是说,他们在所属函数内部被调用的时候自动开辟空间生成,在该函数生命周期结束的时候自动消亡,该空间被释放。

void test()
{
	int a=5;
	cout<<a;
	return;
{

比如上面程序的a,当test函数结束的时候,这个a就从内存消失了。
自动变量(局部变量)存储在栈空间中,这侧面说明了几点。根据栈的特性;

  1. 执行代码的时候,局部变量是依次进栈
  2. 结束代码的时候,这些变量是按相反的顺序依次出栈,也就是释放变量空间
  3. 那么,如果局部变量过多,将会造成栈的溢出,毕竟栈也是有容量限制的,也许正是因为这样,平时使用递归的时候要格外小心,应该递归将使用大量的局部变量,所以当使用递归函数被阻断的时候,那么原因就是栈溢出,栈空间不够了,当然,递归函数也有可能是死循环,有问题.

(2)静态存储
静态存储是整个程序执行期间都存在的存储方式。
有2种方法来声明静态变量;

  1. 在函数外面定义变量,则函数结束生命周期该变量仍然存在,就像npc和现实的人物一样,局部变量就像npc,参与游戏,但是游戏结束的时候就gg。但是静态变量就是真人,参与游戏,游戏结束我仍然存在.
  2. 使用关键字static (static a=5;)

(3)动态存储
new和delete运算符提供了一种比自动变量和静态变量更加灵活的方法。他们管理了一个内存池,也叫做“自由存储空间”,”堆空间“。上面提到,局部变量和静态变量是在栈空间,这显然,这些不同变量存储的位置是不同的。

  1. 在栈中,自动添加和删除机制使占用的内存使连续的,对空间的利用率大,且容易查找。
  2. 在堆中,new和delete的机制导致占用的空间不是那么连续,这样会导致一些隐藏的问题

栈,堆和内存泄漏

实际上,和C语言的malloc,free一样,new和delete必需配套使用。这是为什么呢?
这会导致一个很严重的问题------内存泄漏。
如果使用new开辟一个空间,而不使用delete,那么该空间动态分配的变量或结构会仍然存在,而当函数结束的时候,指向该动态分配空间的指针结束生命周期,(指针变量是自动变量),那么这说明,该动态分配的空间将无法追踪,消失不见,但是仍然占用了堆空间。这会产生一些问题

  1. 动态分配的空间将不可访问,因为指针失效(结束生命周期)了。
  2. 自由存储区越来越小,导致最后没有空间可以利用导致程序崩溃
  3. 甚至给操作系统或在相同内存空间中运行的应用程序带来负面影响,导致他们崩溃
  4. 重启电脑可以使内存释放(如果死机的话)

由此可以看出,虽然指针是C++的最主要的特点之一,但是有利有弊,它是一把双刃剑。

2.模板类vector

vector类似于string类,也是一种动态数组。位于名称空间std。它是使用new创建数组的替代品,更加智能化,更oop,它的一些实现就是new和delete完成,不过它将这些操作包装起来,使程序员使用的时候更加方便,也不会出错。

#include<vector>
using namespace std;

int main()
{
	vector<int>a;  //动态开辟一个int类型变量a
	int n;
	cin>>n;
	vector<double>b(n);   //动态开辟一个double类型数组b
}

3.模板类array

首先,vector的功能毋庸置疑比数组强大,但是,与此付出的代价就是效率低下。如果你能确定需要的数组的长度,第一个选择就是直接定义数组,但是这在使用的时候不是那么安全和方便。因为这个原因,C++11新增了模板类array。

  1. array同意位于名称空间std中,头文件包含array
  2. 于数组一样,array对象的长度是固定的
  3. array也是使用栈(静态内存分配),而不是自由存储区
  4. 效率于数组一样,但是使用方便,更安全
#include<iostream>
#include<array>
using namespace std;
int main()
{
	array<int,5>a;   //开辟一个变量a
	array<double,4>b={1.0,2.1,3.2,4.3};    //开辟一个数组b
}

4.基于范围的for循环

C++11新增了一种循环:基于范围的for循环。这简化了一种常见的循环任务:对数组的每个元素执行相同的操作。

int a[5]={1,2,3,4,5};
for(int x:a)
cout<<x<<endl;

要注意区分的是,这个for里面的x是一个数组元素的一个副本,如果想要改变数组元素,下面这样做是不行的

for(int x:a)
{	
	x+=1;
	cout<<x<<endl;
}

这样只是改变了副本的值,实际上数组a的值没有变。
那么如果说要同时改变数组a的元素,那么要这样,下面是一个例子

#include<iostream>
using namespace std;

int main()
{
	int a[5] = { 1,2,3,4,6 };
	for (int &x : a)   //要加一个引用
	{
		cout << x << endl;
		x += 1;
		cout << x << endl;
	}
		
	return 0;
}

5.文本输入

当我们要输入输出一句话的时候,可以用多种方式来输入,输出。

(1) 使用cin输入
通过while循环和cin输入一句话的时候,可以设置一个”哨兵字符“,将其作为停止输入字符。

#include<iostream>
using namespace std;

int main()
{
	char ch;
	int count = 0;
	cin >> ch;
	while (ch != '#')
	{
		cout << ch;
		++count;
		cin >> ch;
	}
	return 0;
}

这里提一下:
cin>> ,这个函数的过程:先在输入队列找到第一个非空白字符(制表符,空格,回车都是空白字符),然后读取,直到遇到第一个空白字符(回车,制表符,空格)结束读入。

还需要说明一点;虽然是上面的程序遇到‘#’字符将跳出while循环,但是这不代表输入队列的值被清空。也就是说,如果我输入:1234#123!。那么while循环只会输出1234,但是后面的123!却仍然在输入队列,后面再次使用cin>>进行读取的话,输入队列中的123!会直接被读取;

#include<iostream>
using namespace std;

int main()
{
	char ch;
	int count = 0;
	cin >> ch;
	while (ch != '#')
	{
		cout << ch;
		
		cin >> ch;
	}
	while (ch != '!')
	{
		cout << ch;  //cin和cout要反过来,不然输入队列里面的#会被覆盖
		cin >> ch;
	}
	return 0;
}

输出:
在这里插入图片描述
同理,如果!后面还有字符,那么这些字符也会留在输入队列。那么怎么情空呢?
那么就要介绍一个函数了!
cin.ignore(a,ch)
1.cin.ignore()函数是C++标准输入流(cin)中的一个方法。cin.ignore()函数中有两个参数,分别为数值型的a 和 字符型的 ch ,即cin.ignore( a, ch )。它表示从输入流 cin 中提取字符,提取的字符被忽略,不被使用。而每抛弃一个字符,它都要进行计数和比较字符:如果计数值达到 a 或者被抛弃的字符是 ch ,则cin.ignore() 函数执行终止;否则,它继续等待。

2.它的一个常用功能就是用来清除以回车结束的输入缓冲区的内容,消除上一次输入对下一次输入的影响。例如可以这么用,cin.ignore(1024, ‘\n’),通常把第一个参数设置得足够大,这样实际上是为了只有第二个参数 ‘\n’ 起作用,所以这一句就是把回车(包括回车)之前的所以字符从输入缓冲流中清除出去。

3.如果默认不给参数的话,默认参数为cin.ignore(1, EOF),即把EOF前的1个字符清掉,没有遇到EOF就清掉一个字符然后结束。

(2)使用cin.get(ch)
可以看到,单纯用cin来解决字符串的输入还是有局限,毕竟cin不会读取空白字符。那么istream的成员函数cin.get(ch)就很好的解决这个问题

#include<iostream>
using namespace std;

int main()
{
	char ch;
	int count=0;
	cin.get(ch);
	while(ch!='#)
	{
		cout<<ch;
		++count;
		cin.get(ch);
	}
	cout<<endl<<count;
	return 0;
}

值得注意的是:这个程序有一个明显的错误!cin.get(ch)调用将一个值放在ch变量中,这意味着将改变该变量的值。在C语言中,要修改变量的值,必须将变量的地址传给函数。但是这里却没有这样做,不是用的&ch,而是直接用的变量值ch。我们知道这在C语言中是不允许的,但在C++中可以这样做。这是为什么?

因为在C++中有一个东西叫引用,只有函数在参数列表中将参数声明引用,那么在实际参数中,不需要额外添加&。而istream类将cin.get(ch)的参数声明为引用类型,因此该函数可以修改其参数的值。

(3)文件尾条件
实际上,通过设置哨兵字符来终止输入这种方式很不好。因为很有可能哨兵字符本身就是需要读入的一部分,那么就会产生误会。明明还没有结束读入,却提前结束读入。那么下面介绍一种很好的方式

如果输入来自于文件,则可以用一种特点的方式----EOF(检测文件为尾)。C++输入工具和操作系统协同工作,来检测文件尾并将这种信息告知程序。
实际上,操作系统支持重定向,允许用文件替代换键盘输入。例如:假设在Windows中有一个名为gofish.exe的可执行文件和一个名为fishable的文本文件,则可以在命令提示符模式下输入下面的命令:

gofish<fishable

这样,程序将从fishable文件(而不是键盘)获得输入。<符号是Windows和Unix命令提示符模式的重定向运算符

由此,操作系统允许通过键盘来模拟文件尾条件。在Unix系统里,在行首按下Ctrl+D来实现;在Windows命令提示符模式下,可以在任意位置按Ctrl+Z和Enter。

具体实现:
检查到EOF后,cin将eofbit和failbit都设置为1。那么怎么证明呢?答案是可以通过成员函数eof()来查看eofbit是不是被设置,如果检查到EOF,cin.eof()将返回true,否则返回false。cin.fail()同理。通常,EOF被定义为-1

#include<iostream>
using namespace std;

int main()
{
	char ch;
	int count = 0;
	cin.get(ch);
	while (cin.fail() == false)  //同时按ctrl+z,然后按回车,回车不要一起按
	{
		cout << ch;
		++count;
		cin.get(ch);
	}
	cout << endl << count;
	return 0;
}

这种方式固然很好,但是不要忘记了,刚刚说到,使用了EOF后,cin将failbit和eofbit设置为1,这说明说明?表示后面的cin将失去读取的资格

#include<iostream>
using namespace std;

int main()
{
	char ch;
	int count = 0;
	cin.get(ch);
	while (cin.fail() == false)
	{
		cout << ch;
		++count;
		cin.get(ch);
	}
	cin >> ch;  //这句话将被跳过
	cout << endl << count;
	return 0;
}

运行上面的程序会发现跳出while循环之后,后面的cin语句将失去读取数据的资格。

那么怎么办?
使用cin.clear()清除EOF标记

还有很有趣的一种写法是:

while(cin)
{...}
while(cin.get(ch))
{...}

这种做法的依据是:cin,get(ch)函数返回一个cin对象,然而,istream类提供了一个可以将istream对象转换为bool值的函数,这样,当cin出现在需要bool值的地方,该转换函数将被调用。而且,这种写法比cin.fail()更好,因为它可以检测到其它错误,比如磁盘故障

cin.get(char)成员函数如果调用通过则返回转换为false的bool值来指出已达到EOF,而cin.get()成员函数调用则通过返回EOF的值(通常是-1)来指出已到达EOF,EOF是在istream里面定义的。

今天的c++学习到这里,继续加油!!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值