C++学习——第7章 指针(下)

1.       使用指针对字符串排序

以下代码有问题!编译的时候没问题,但是输入后回车,会出错,具体原因不知道!

#include <iostream>
#include <string>

using namespace std;

int main()
{
	string text;
	const string separators = " .,\"\n";
	const int max_words = 1000;
	string words[max_words];
	string* pwords[max_words];

	cout << endl << "Enter a string terminated by #: " << endl;
	getline(cin, text, '#');

	int start = text.find_first_not_of(separators);
	int end = 0;
	int word_count = 0;
	while(start != string::npos && word_count < max_words)
	{
		end = text.find_first_of(separators, start + 1);
		if(end == string::npos)
			end = text.length();

		words[word_count] = text.substr(start, end - start);
		pwords[word_count] = &words[word_count];
		word_count++;

		start = text.find_first_not_of(separators, end + 1);
	}

	int lowest = 0;
	for(int j = 0; j < word_count - 1; j++)
	{
		lowest = j;

		for(int i = j + i; i < word_count; i++)
		{
			if(*pwords[i] < *pwords[lowest])
				lowest = i;
		}
		
		if (lowest != j)
		{
			string* ptemp = pwords[j];
			pwords[j] = pwords[lowest];
			pwords[lowest] = ptemp;
		}
	}

	for (int z = 0; z < word_count; z++)
		cout << endl << *pwords[z];

	cout << endl;
	return 0;
}

以上代码具体原因不知道,暂且先放下,但是代码内容基本正确,可以参考。

 

如果想使输出结果更整齐:比如说大量的相同首字母的单词分组输出,输出效果会更加整齐

	//分组输出
	char ch = (*pwords[0])[0];		//第一个单词的第一个字母
	int words_in_line = 0;			//一行的单词计数
	for (int i = 0; i < word_count; i++)
	{
		if(ch != (*pwords[i])[0])		//判断是否是新的首字母,只要显示的单词第一个字母不同于ch中的字符,就开始一个新组,同时输出一个换行符
		{
			cout << endl;				//开始新的输出行
			ch = (*pwords[i])[0];		//保存新的首字母
			words_in_line = 0;			//重置一行的单词计数
		}
		cout << *pwords[i] << " ";
		if (++words_in_line == 6)		//每6行就输出一个换行符
		{
			cout << endl;				
			words_in_line = 0;			//新的单词输出行
		}
	}

注意,我们引用了第一个string对象的第一个字符,表达式*pword[0]解除了pwords数组中第一个元素的指针的引用,给出了该指针指向的单词。

而表达式(*pwords[0])[0]给出了该单词的第一个字母,因为圆括号外的方括号包含了单词中的字母的索引。

由于方括号的优先级高于间接运算符*,因此用圆括号分开。

 

 

1.       常量指针和指向常量的指针

指向常量的指针。指针指向的内容不能修改,但可以把指针设置为其他内容:

const char* pstring = “Some tet that cannot be changed”;

当然,也使用与其他类型的指针:

const int value=20;

const int* pvalue=&value;

value是一个常量,不能修改。pvalue是一个指向常量的指针,可以用于存储value的地址。

不能再不是常量的指针中存储value地址(因为这意味着可以通过指针修改常量),但可以把不是常量的变量的地址赋予pvalue。在后一种情况中,通过指针修改变量是非法的。

一般情况下,总是用这种方式加强常量的不变性,相对减弱常量的不变性是不允许的。

 

常量指针。存储在指针中的地址不能修改。

像这样的指针只能指向初始化时指定的地址。但是,地址的内容不是常量,可以修改。

int value=20;

int* const pvalue=&value;

这个语句声明,指针pvalue时const,只能指向value。不能指向另一个变量。但是value的内容不是const,所以可以随时修改。

但是如果value声明为const,就不能用&value初始化pvalue,指针pvalue只能指向不是常量的int类型的变量。

 

指向常量的常量指针。因为存储在指针中的地址和该指针指向的内容都声明为常量,所以两者都不能修改。

const int value=20;

value现在是一个常量,不能修改它。但是仍可以用value的地址来初始化一个指针:

const int* const pvalue=&value;

pvalue现在是一个指向常量的常量指针,不能修改pvalue指向的内容,也不能修改它包含的地址上的值。

注意:

一般情况下,这并不仅限于char和int类型指针,可以应用于任意类型。

 

2.       指针和数组

指针的算术运算:只能对指针进行加减运算,但可以比较指针,得到逻辑结果。

pvalue++;

pvalue +=1;

都是可以的,指针加上一个整数,结果是一个地址。减去一个整数,结果也是一个地址。

这个可以运用于数组的操作,因为数组的存储连续的,每个数组元素的地址之间相差4位。

 

使用数组名的指针表示法

data[3]等价于*(data+3)

即,data[0]等价于*data,因为指针指向的是数组的第一元素,而数组的第一个元素可以表示为data[0]。

代码:把数组名用作指针

下面代码对数组使用指针,在一个面向数值的程序中计算质数。

质数是只能被1和它本身整除的整数。

#include <iostream>
#include <iomanip>

using namespace std;

int main()
{
	const int max = 100;				//集合中质数个数
	long primes[max] = {2, 3, 5};		//质数集合的前三个数
	int count = 3;						//质数个数
	long trial = 5;						//下个测试的数
	bool isprime = true;				//判断一个数是否为质数

	do
	{
		trial += 2;		//下一个值的检测
		int i = 0;

		//循环中的算法依据:任何不是质数的整数都可以被更小的质数整除。
		do
		{
			isprime = trial % *(primes + i) > 0;		//不能整除
		} while (++i < count && isprime);				//当i的值达到count或者isprime为flase(可以被整除,即trial不是质数),就跳出循环,如果trial不能被primes数组中的所有质数整除,isprime就是true,循环在i达到count时结束。

		if (isprime)									//判断是否被整除,如果不能被整除,那么就说明是质数,将trial中的值存储在primes[count]中,再递增count。
			*(primes + count++) = trial;
	} while (count < max);

	//一旦找到max个质数,就显示它们,一行显示5个质数,字符宽度设置为10个字符
	for (int i = 0; i < max; i++)
	{
		if (i % 5 == 0)		//当i的值为0、5、10等值时,就另起一行
			cout << endl;
		cout << setw(10) << *(primes + i);		//设置每个字符之间宽为10个字符
	}
	cout << endl;
	return 0;
}

实际上,如果当前整数只能被小于或等于该整数的平方根的质数整除,就需要测试,因此以上代码不高效。

 

 

对多位数组使用指针

通常方式使用数组名,带有两个索引值

beans[i][j]

指针表示法中使用数组名

*(*(beans+i)+j)

从内层开始确定这个表达式的含义。

beans表示数组中第一行的地址,beans+i表示数组的第i行。表达式*(beans+i)是第i行上第一个元素的地址,*(beans+i)+j是第i行上第j个元素的地址。整个表达式*(*(beans+i)+j)就是该元素的值。

 

C样式字符串的操作

<cctype>头文件中声明的函数,可以分析和转换单个字符。

<cstring>头文件中声明的一些函数,可用于分析和转换非空字符串的函数。(不要把<cstring>和<string>混淆,后者定义了string类型。

连接字符串——strcat()函数和strncat()函数

函数strcat()带两个参数,分别是char*类型和const char*类型的字符串,该函数把后一个参数追加到前一个参数中,在这个过程中修改第一个参数。它会改写第一个字符串尾部的’\0’,并把’\0’加到组合字符串的最后,单用户应确保第一个参数指向的字符串有足够的空间容纳结果。第一个参数也是函数的返回值(因为指向的第一个参数,也就是指向了整个字符串)。

         函数strncat()带有三个参数,前两个参数与strcat()相同。第三个参数是一个整数,指定把第二个字符串的多少个字符加到第一个字符串上。如果指定的字符个数多于字符串中的字符个数,就追加整个字符串。除此之外,该函数使用条件和返回值与strcat()相同。

使用:

char name[50] = “Bing”;

char surname[] = “Crosby”;

下面的语句把一个空格追加到name字符串上:

strcat(name, ” ”);

接着,追加surname:

strcat(name, surname);

结果是name包含字符串“Bing Crosby”。因为函数总是返回第一个参数。

故可以用下面的语句完成上面两个操作:

strcat(strcat(name, “ ”), surname);

语句较难理解。

验证第一个参数中是否有总够的空间容纳组合字符串,可以使用strlen()函数,这个函数返回其唯一参数的字符串的长度。

if(sizeof name/sizeof name[0] > (strlen(name) + strlen(surname) + 1))

std::cout << strcat(strcat(name, “ ”), surname);

 

 

1.       动态内存分配

在程序中使用固定的变量集合是非常受限制的,而且常常比较浪费。

使用动态内存分配,意味着在程序运行时(运行期间)为正在处理的数据分配存储它所需要的内存空间,而不是在编译它时(编译期间)分配空间。

 

自由存储区

在大多数情况下,程序执行时计算机上总是有未使用的内存。在C++中,这些未使用的内存称为自由存储区,有时称为堆(heap)。使用一个特殊的C++运算符,就可以把自由存储区中的空间分配给给定类型的新变量,并返回所分配空间的地址。

这个运算符就是new,同它对应的运算符是delete,它可以释放以前用new分配的内存。

注意:在使用new为变量分配内存空间时,就是在自由存储区创建该变量。在变量占用的内存没有用运算符delete释放前,该变量将一直存在。

 

运算符new和delete

用法:

double* pvalue = 0;

pvalue = new double;

注意,所有的指针都应初始化。

可以初始化用new创建的变量。

pvalue = new double(999.0);

当不再需要动态分配内存的变量时,就可以用delete运算符,释放它在自由存储区中占的内存:

delete pvalue;

上面这个语句,将确保该内存可以在以后由另一个变量使用。如果没有使用delete,后来又在指针pvalue中存储了另一个地址,就不能释放原来的内存空间,也不能使用它包含的变量,因为不能再访问该地址了。

注意:delete运算符释放了内存,但没有改变指针。运行完上面的语句后,pvalue仍包含已分配给它的内存地址,但该内存现在已经自由了,可以立即分配给其他实体(比如另一个程序)。为了避免使用包含垃圾地址的指针,除非要重新给它赋值或它已超出作用域,否则就应在释放内存时重新设置改指针,如下:

delete pvalue;

pvalue = 0;

 

数组的动态内存分配

new:

pstring=new char[20];

delete:

delete [] pstring;

注意:这里的方括号非常重要,表示要删除的是一个数组。从自由存储区中删除数组时,应总是包含方括号,否则结果是不可预料的。还要注意不能在这里指定维数,只写上[]就可以了。


 

动态内存分配的危险:

A.      内存泄露

在使用new分配内存空间时,由于在使用完该内存后没有释放它,从而会出现内存泄露。

指针和变量作用域

如果指针包含自由存储区中某个内存块的地址,在该指针超出作用域时,就不能释放该内存块了。

B.      自由存储区的碎片

内存碎片在频繁地分配和释放内存块的程序中会出现。

如果穿件并释放许多不同大小的变量,所分配的内存就有可能散布在小的自由内存块。每个小内存块都不足以容纳程序需要的新动态变量。

这个情况现在比较少见,现代的计算机,虚拟内存提供了非常大的内存。在发生这个问题时,解决方法是避免分配较小的内存块,或者自己分配较大的内存块,并管理内存的使用。

 

 

转换指针

运算符:reinterpret_cast<>()

上面这个运算符允许强制转换任何指针类型,其限制是如果要强制转换的指针类型声明为const,就不能把它强制转换为不是const的类型。

reinterpret_cast<指针类型>(表达式)

例如:

float value=2.5f;

float* pvalue=&value;

long* pnumber= reinterpret_cast<long*>(pvalue);

这个过程的逆过程——存储long值,并把它解释为float类型——肯定会生成一个格式不正确的浮点数。

强调:reinterpet_cast<>()运算符是很危险的,因为它提供了把一种类型的值随意解释为另一种类型的方式。应该避免使用这个运算符。


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值