向字符数组循环输入单个后c语言输出单个字符出错

5073人阅读
Algorithm(16)
问题:输入10个字符串,对其进行排序(由小到大)后输出。
算法:& 假设输入的每个字符串中的字符个数不超过10个,那么输入十个就是十行十列的字符数组,那么就需要有一个指针可以访问行,即需要一个指向一维数组的指针,再仿照貌似冒泡排序对其进行排序然后进行输出.
#include &stdio.h&
#include &string.h&
void main()
{ void paixu(char (*p)[10]);
char a[10][10],(*p)[10],i;
printf(&请输入10个字符串(每个字符串长度不大于10):\n&);
for (i=0;i&10;i++)
scanf(&%s&,a[i]);//按行输入
p=a;//P指向二维数组的第0行,注意不能写成p=a[0]
printf(&排序后为:\n&);
for (i=0;i&10;i++)
printf(&%s\n&,a[i]);//按行输出
void paixu(char (*p)[10])
//注意:此时P已经指向二维数组的首行了
char temp[10],*t=//这里不能直接定义一个*t,因为(*p)[10]必须指向一维数组
for (i=0;i&9;i++)//9趟排序
for (j=0;j&9-i;j++)
if (strcmp(p[j],p[j+1]) &0 )//只要前一行大于后一行就交换
strcpy(t,p[j]);
/*这里不好理解,p[j]就是p指向j行*/
strcpy(p[j],p[j+1]);
strcpy(p[j+1],t);
& 算法2:上面的排序是将是个字符串作为二维数组处理的, 现在就利用指针数组来存储这是个字符串。
#include &stdio.h&
#include &string.h&
void main()
void paixu(char *x[10]);
//在这里完全可以定义为*a[n]实现动态建立数组
char *a[10],str[10][10];
for (i=0;i&10;i++)
a[i]=str[i];
//将第i行的字符串的地址给a[i]数组的第i个元素
printf(&请输入十个字符串:\n&);
for (i=0;i&10;i++)
scanf(&%s&,a[i]);
printf(&排序后为:\n&);
for(i=0;i&10;i++)
printf(&%s\n&,a[i]);
void paixu(char *x[10])
int i,j,k;
//选择排序
for (i=0;i&9;i++)//n-1趟即9趟排序
k=i;//记录每趟的首元素
for(j=i+1;j&9;j++)//每趟都从这一趟的首元素开始比较,直到末尾,所以起始点是i+1
if (strcmp(x[k],x[j]) & 0)
k=j;//记录最小值
if (k!=i)//若最小值不是每趟首元素就交换
strcpy(t,x[k]);
//指针数组a[k]就是指向第k个字符串
strcpy(x[k],x[i]);
strcpy(x[i],t);
算法三:在上面的基础上再次改动,利用指向指针的指针,排序算法不变
#include &stdio.h&
#include &string.h&
void main()
void paixu(char **x);
//在这里完全可以定义为*a[n]实现动态建立数组
char *a[10],str[10][10],**p;
for (i=0;i&10;i++)
a[i]=str[i];
//将第i行的字符串的地址给a[i]数组的第i个元素
printf(&请输入十个字符串:\n&);
for (i=0;i&10;i++)
scanf(&%s&,a[i]);
printf(&排序后为:\n&);
for(i=0;i&10;i++)
printf(&%s\n&,a[i]);
void paixu(char **x)
int i,j,k;
//选择排序
for (i=0;i&9;i++)//n-1趟即9趟排序
k=i;//记录每趟的首元素
for(j=i+1;j&9;j++)//每趟都从这一趟的首元素开始比较,直到末尾,所以起始点是i+1
if (strcmp(*(x+k),*(x+j)) & 0)
k=j;//记录最小值
if (k!=i)//若最小值不是每趟首元素就交换
strcpy(t,*(x+k));
//指针数组a[k]就是指向第k个字符串
strcpy(*(x+k),*(x+i));
strcpy(*(x+i),t);
&&& 三种方法实质都是想法设法的去访问二维数组。第一种,即指向一维数组的指针,将p=a,则p就指向了a的首行,而它的每一个元素(*p)[0],(*p)[1],(*p)[2],(*p)[3]...对应的则是这一行上的元素;第二种,即数组指针,其本身数组元素存放的就是地址,所以需要对每个数组的元素赋值,即循环10次的进行a[i]=str[i],即把第每一行的地址给数组里的元素,而数组里存的是地址,因而是将每一行的地址存在了对应的数组里,这样a[0]一直到a[9]指向的就是从0行到9行的首地址。第三种方法其实和第二种没区别,就是间接的又指向了另一个指针,本质都一样。主要注意前两种方法的区别。
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:404491次
积分:4196
积分:4196
排名:第4453名
原创:85篇
评论:168条&&国之画&&布布分享&&&& &&&&
版权所有 京ICP备号-2
迷上了代码!白话 C++ 第十六章 数组(一)
第十六章 数组(一)
引子 -- “小王”成绩管理系统
小王老师参加了“编程摇篮”的学习,这一天他的系主任请他写程序。
系主任提的第一个要求是:
用户输入6个班级的各自的学生英语成绩总分,要求程序输出成绩总分和平均分。
“这简单了!”小王心想,“在前面的课程里,早就有过成绩统计的例子了嘛!改改就行。”
“小王成绩管理系统Ver1.0”
功能:求六个班级的成绩总分及平均分 (为了方便起见,下面的所有成绩数据都使用整型,不考虑小数)
int cj,zcj=0,pjcj=0;& //各班成绩,总成绩,平均成绩
for(int i=0; i&6;i++)
& cout && &请输入第& && i+1 && &班的分数:&;
& zcj +=& //累加总成绩
//求平均:
pjcj = zcj / 6;
cout && &总成绩:& && zcj &&
cout && &平均成绩:& && pjcj &&
小王迅速测试了一遍,没有问题,任务胜利完成!小王的形像立刻在系主任的眼里高大起来……不过,客户的需求总是在不断进步的!系主任立即提出第二个要求:
必须加入查询功能,比如说,用户输入1,则程序输出一班的成绩,输入2,则输出二班的成绩,以此类推。
“这可不好办了!南郁老师没有教我这个啊……”小王心里很着急,“要让用户可以查询,那至少我得让程序把这6个成绩记下。”小王来回看了好几遍for循环,也没有想出如何在for循环里记下用户输入的成绩。他决定放弃了……等等!不就6个班级吗?我不用循环
总行吧,在讲循环流程的那一章里,不是举了一个最笨的办法吗?(见
(尽管这样写程序,按南郁老师说只能得到“鸭蛋”,可以桌子那边传来系主任殷切的目光……)
一阵“噼噼叭叭”,小王删除了前面所有代码,可以实现查询的新代码如下:
“倒退的2.0”
功能:求六个班级的成绩总分及平均分,并可实现对各班成绩查询。
//定义六个变量,用于存储六个班级的成绩:
int cj1,cj2,cj3,cj4,classSocre5,cj6;
//老师说下面的方法很“笨”,不过没办法了,只能这样...
//(反复用 Ctrl+C,Ctrl+V 还是很方便的)
cout && &请输入第1班的成绩:& &&
cin && cj1;
cout && &请输入第2班的成绩:& &&
cin && cj2;
cout && &请输入第3班的成绩:& &&
cin && cj3;
cout && &请输入第4班的成绩:& &&
cin && cj4;
cout && &请输入第5班的成绩:& &&
cin && cj5;
cout && &请输入第6班的成绩:& &&
cin && cj6;
//求总成绩和平均成绩:
int& zcj = (cj1+cj2+cj3+cj4+cj5+cj6);
int& pjcj = zcj / 6;
cout && &总成绩:& && zcj &&
cout && &平均成绩:& && pjcj &&
//下面是新功能:查询:
& cout && &请输入要查询的班级次序(1~6,0:退出)& &&
& //用switch实现对用户输入的判断及分流:
& switch(c)
&&&&& case '1' : cout && cj1 &&
&&&&& case '2' : cout && cj2 &&
&&&&& case '3' : cout && cj3 &&
&&&&& case '4' : cout && cj4 &&
&&&&& case '5' : cout && cj5 &&
&&&&& case '6' : cout && cj6 &&
&&&&& //其它的,输出出错消息:
&&&&& default :& cout && &您的输入有误!& &&&&&&&
while(c != '0') ; //用户输入‘0’,表示退出
手有点酸,不过毕竟实现了查询功能。小王把写好的程序拿到系主任那里一运行,主任的眼睛透过厚厚的眼镜片闪闪发光……并且开始打听“没有弯路,编程摇篮”的网址。然后他说:“小王,你这个程序太好了!我们完全有必要把它发扬光大!你这就把程序稍作修改,扩展到整个学校的5000个学生的成绩都可以查询……”
“5000个学生?我岂不要定义5000个成绩变量……”小王眼前一黑,倒在地上。
数组的定义及使用
请从下面的实例中讲解,数组变量与我们以前学过的普通变量在定义和使用上有什么区别。
处理5000个学员的成绩,是否需要定义5000个变量?
现实生活中有大量的数据类似于此,它们有相同的类型,需要的处理的方法也一致。
为了实现对这些数据统一表达和处理,C,C++提供了“数组”这一数据结构。
数据类型 数组变量名[个数常量];
和普通变量定义的语法:数据类型 变量名;相比,主要是多了一个“[个数]”。
定义数组时,方括号内的数值指明该数组包含几个元素;使用数组时,方括号内的数值表示:使用的是第几个元素。为了直观,我假设可以用汉字来命名变量:
int 抽屉[100];
这里定义了一个“抽屉数组”,这个数组包含了100个抽屉。
而在使用时:
int a = 抽屉[10];
这里的10表示我们要使用第10个抽屉(其实是第11个,见后)。
还是来看小王老师的学生成绩管理系统吧。因为主任说要管理5000个学生成绩,那么,我们就得定义一个成绩数组,包含5000个元素。
int& cj[5000];
这一行我们就相当于定义了5000个int类型的变量。这5000个变量有个统一的名字:cj。为了区分出每个变量,还需要使用“[下标]”的格式指明。下标从0开始,一直到4999。比如,第一变量是:
cj[0],第二个为:cj[1]……最后一个是cj[4999]。
强调:在C,C++中,数组的下标从0开始。因此,我们定义了数组 int cj[5000];得到的是从cj[0] ~
cj[4999]的变量,而不是cj[1] ~ cj[5000]。
比如,让第100个学员的成绩为80:
cj[99] = 80;
为什么是99而不是100?因为数组的下标从0开始。
再比如,让变量a等于第100个学员的成绩:
int a = cj[99];
有了“数组”这个新武器,要解决前面全校成绩查询问题,一下子变得简单了。
//定义一个含5000个元素的数组,用于存入5000个学生的成绩:
int cj[5000];
//总成绩,平均成绩:
int zcj=0,
//仍然可以用我们熟悉的循环来实现输入:
for(int i=0; i&5000; i++)& //i从0开始,到第49999(含)个结束。
&& cout && &请输入第& && i+1 && &学员的成绩:&;
&& cin && cj[i];&&&&& //输入数组中第i个元素
&& //不断累加总成绩:
&& zcj += cj[i];&&&&&&&&&&&
//平均成绩:
pjcj = zcj / 5000;
cout && &总成绩:& && zcj &&
cout && &平均成绩:& && pjcj &&
//下面实现查询:
&& cout && &请输入您要查询的学生次序号(1
~ & && 5000 && &):& ;
&& if( i &= 1 && i &= 5000)
&&&&& cout && cj[i-1] &&
//问:为什么索引是 i-1,而不是 i ?
&& else if( i != 0)
&&&&& cout && &您的输入有误!& &&
while(i != 0);& //用户输入数字0,表示结束。
代码用了两个循环——是的,有了数组,你会发现循环流程将用得越来越多——第一个
循环用以输入5000个成绩。第二个do...while 循环用以查询。用户可以输入1到5000之内的数字(和现实生活习惯一致)。
下面要复习“宏”的知识。如果学校多了一个学生,那么我们就得将上面所有以斜体标出的“5000”改为5001。这是一件让人觉得无趣,且很容易出错的工作。另外,现在我们要调试这段程序,难道你真的有耐心输入5000个成绩?在调试时,我们可能希望只输入5个成绩。
这时我们可以使用“宏”。下面的代码中,先是定义了一个宏:MAX_CJ_COUNT,而后所有立即数5000都用它来表示。当我们需要改变人数时,只需更改MAX_CJ_COUNT的宏定义。
//定义一个含5000个元素的数组,用于存入5000个学生的成绩:
#define MAX_CJ_COUNT& 5000 //定义一个宏,方便修改为实际学生人数。
int cj[MAX_CJ_COUNT];
以下代码略。有关宏的用处,请复习:
除非确实可以一次决定某数组的大小,否则,使用一个意义明确的宏来定义数组的大小,总是一个不错的主意。
个数定义必须是常量
再一看眼数组定义的语法:
数据类型 数组变量名[个数常量];
注意“常量”两字,这说明,个数必须是一个可以事先决定的值,并且该值不能被改变。
比如用立即数:
int arr[5000];
或者用宏:
#define MAX_CJ_COUNT& 5000
int arr[MAX_CJ_COUNT];
或者用常量:
const int max_cj_count = 5000;
int arr[max_cj_count];
就是不能用变量:
int max_cj_count =& 5000;
int arr[max_cj_count];&& // error! 不能用变量指定数组的大小。
为什么呢?因为数组占用的内存空间大小必须在程序编译时决定,并且一旦决定了,就不能再改变。所以只能用常量(不变的量)来指明数组的大小。当然,这是指在数据区或栈区分配内存(和程序有一样的全存期),如果是在堆区,则可以动态地分配数组的大小,这些我们在指针一章里讲。
有关常量和变量的区别,如果有所不清,请前面章节内容。
如何给数组中的元素赋值
如果把单个变量看成是“游兵散勇”的话,那么数组对应的是“集团”。集团的“兵”就是我们前面说的数组的元素。这些“兵”不再有单独的名字,而是统一使用编号来区别,这个编号,我们称为“下标”。
在和数组打交道时,我们需要分清:是对整个数组操作,还是对数组中的单个元素进行操作。
在定义数组时初始化。
普通变量可以在定义时同时赋初值:
int a = 100;
也可以在定义以后才赋值:
对于数组变量,则只能在定义时,对整个数组赋初值:
数据类型 数组变量名[个数] = {元素1初值,元素2初值,};
即,将初值用一对 {} ( 花括号 )括起来,相邻的值之间用逗号分隔。
int arr[10] = {9,8,7,6,5,4,3,2,1,0};
上面定义一了个数组 arr,共10个元素。初始值为从9到0。即,执行上面代码以后,arr[0]值为10,arr[1]值为9……arr[9]值为0。
在初始化赋值是时,注意所给值的个数不能超过数组的大小,比如:
int arr[10] = {10,9,8,7,6,5,4,3,2,1,0};& //错误!越界了
你定义了数组为10个元素,可是你却赋给它11个值,这称为数组越界:你在宾馆里预定了10间房,你却让11个人去住,多出的那个人挤在哪里呢?编译器遇上这类问题时,很明智地停了下来。
不过,你可以给它少于定义个数的初值:
int arr[10] = {9,8,7,5};&&&&&&&&&&&&&&
你定义了数组为10个元素,但你可以给它少于10个的初始值。那些没有得到初始值的元素值是多少呢?对于全局变量来说,将被编译器初始化为0,对于局部变量而言,则是未定义的值(不可预测的值)。如果只定义数组,没有对其进行任何初始化,同样适于本情况。所有元素的初值均依本数组是全局或局部变量而定,为0或未定义值。
可以跳过一两个元素不初始化吗?如:
int arr[10] =& {9,,7,,6};& //跳过中间的某些元素,C:OK;C++: Error。
因为我们主要学习C++,所以认为跳过数组中某些元素的初始化赋值是错误的。
也就是说,你尽可以先预定下多个房间,然后先只派部分人去住。不过C说:允许你们跳着房号住,而C++则要求依次住下,把空房留在后面。
你还可以不指定数组元素个数,直接通过对其初始化来让编译器得到它的实际个数:
int arr[] = {9,8,7};& //元素个数为: 3
即,在未指定义大小时,则初始值的个数就是数组元素的个数。
不过,你不能既不指定数组元素个数,也不进行初始化:
int arr[];&& //Error, 到底是几个元素?
这也没有什么不好理解的,去几个人,就开几个房间。让宾馆老板自已去数人头,我们不必非得自已报人数——前提是,房客一个一个的都得事先现身。
在定义之后为元素赋值
很多时候,我们无法在定义数组的同时就知道数组中各元素的值,所以,这时需要在定义以后各数组中的各个元素赋值。记住,此时只能对单个元素进行直接操作。这和普通变量不一样,下面的代码是错误的:
int arr[5];
arr[5] = {1,2,3,4,5}; //错,在编译器看来,arr[5]是数组arr的第6个元素。
arr = {1,2,3,4,5};&& //错,仍然不行。
这一点和普通变量不一样。也就是说,对数组整体的初始化,只能在定义时实行。
大都数情况,我们这样改变数组中某个元素的值:
int arr[5];
arr[0] = 95;&
arr[1] = 89;
arr[2] = 100;
前面关于成绩管理的例子中,已经有过如何改变数组元素值的代码:cin && cj[i];
这一句将用户输入的成绩赋给数组cj中的第i个元素(i从0计起)。
两个数组可以相互赋值吗?答案也是不行:
int arr1[5] = {1,2,3,4,5};
int arr2[5];
arr2 = arr2; //不行!整个数组之间不能直接相互赋值。
你可能很不理解为什么上面的代码会出错,不过在学习后面有关指针及内存地址的更多知识以后,你会明白。现在我们只能告诉你,以我们的所学,可以方便地用一个循环来
实现将一个数组内的值全部赋值给另一个数组,这也称为数组间的拷贝。
for(int i=0;i&5;i++)
&&& arr2[i] = arr1[i]; //正确,数组元素之间可以相互赋值。
当然,这样做可一定要注意:当两个数组的元素个数不一致时,不要越界访问:
int arr1[5] = {1,2,3,4,5};
int arr2[6];
for(int i = 0;i&6;i++)& // i 从 0 到 5
&&& arr2[i] = arr1[i];
arr2有6个元素,而arr1只有5个。当循环执行到 i为5时,访问arr1[5]将造成越界。
(越界可以直观地理解为“越出边界“,即越出数组的边界,访问到了数组以外的内存,其内容将是未知的。)
控制台下如何输入和输出数组
输入和输出属于程序设计中,用户界面这一方面的内容。在Windows图形界面和控制台或DOS的字符界面下会完全不同。
先说输入:
以前要让用户输入个某个变量的值,很简单,比如让用户输入一个学成成绩(整型):
现在,cj是一个数组: int
那么要输入这5个成绩,是否可以直接写:cin &&
你可能猜到了:不行。不信可以试试(建议:不管你信不信都请试试)。
为什么不行?原因是当 cin 碰上 cj时,它只知道
cj 是整型数组,但它不知道这个数组多大。你可能又要问,为什么cin能知道cj是一个数组,却不能知道这个数组有多大呢?这又涉及到变量与内存关系。我们先来看这个图:
(图:,cin只“看”到数组中的第一个元素)
数组中的各个元素总是连续地存入在内存里。所以在C,C++里,为了效率,数组变量的传递,总是只传递第一个元素的内存地址,(后面的元素自然紧跟着)。所以,当cin得到
cj这个数组时,它只看到第一个元素,它知道第一个元素的大小,却并不知道整个数组的大小,也不知道到底有多少个元素在这个数组里。这就有比你是一个窗口的售票员,你知道外面来买票的是一个队伍,但你不知这个队伍到底有多少。当然,C、C++也提供“卖团体票“的函数,但那就需要向这个函数额外传递一个数组的大小的参数。
说了这么长关于输入的问题,其实解决方法我们早于用过,来一个循环流程,一个一个输入即可:
int arr[5];
for(int i=0;i&5;i++)
&& cin && arr[i];
再说输出。
可想而已,想一次性输出一个数组中的各元素的值,也是不可能的:
int arr[5] = {1,2,3,4,5};
cout && //并不能输出arr内5个元素的内容。
你不能就此放过这个问题,我一直建议要多动手,多试试,这次就可以看出作用。当你试着在C++
Builder内写上面的代码,然后编译,你会发现编译器并没有报错。程序也可以运行。
请照着下面的代码“试试”:
#include &iostream.h&
int main(int argc, char* argv[])
&&& int arr[5] = {1,2,3,4,5};
&&& cout && //提问:输出一个数组,结果会是什么?
&&& cin.get();& //从本章起,不再使用getchar();改为本行
这运行结果截图:
输出了一个怪怪的数值。这个数值正是数组 arr
在内存中的“门牌号”——地址。并且,数组的地址其实也正是数组中第一个元素的地址。关于数组与内存地址的关系。我们在下一章详细讲解。这里要关心的正事是如何输出数组中的每个元素的值,代码如下:
int arr[5];
for(int i=0;i&5;i++)
&& cout && arr[i];
同输入一样,还是对元素操作。
数组输入输出练习
现在请大家打开CB,新建一控制台工程。实现以下要求。
接受用户的键盘输入10个整数,然后输出这10个数,每行输出1个数。
1、别忘了文件开始位置加入本行:#include &iostream.h&
2、由于Windows操作系统的问题,使得其控制台环境下,getch()或cin.get()在前面有用户输入数值并且回车的情况下,将直接接受回车字符,导致起不到“暂停”的作用。解决方法为:使用两行:
cin.get();
cin.get();
getchar();
getchar();
从本章开始,我们将改为使用cin.get()来起暂停作用。请大家注意。
数组的尺寸
这里的尺寸说的是数组占用内存空间的大小,即某个数组占用了多少个字节。
以前我们说一个int型数据占用4个字节,那么一个有n个元素的整型数组占用了多少个字节?几乎可直接猜到:4
* n 个字节。而一个有n个元素的字符型或bool型数组,则应占用
1 * n 个字节,因为它们单个元素的大小均为1字节。
我们用sizeof测量某个变量占用的字节数:
int size = sizeof(a);
size 将得到 4。
同样,我们可以这样来得到数组的尺寸:
int arr[10];
int size = sizeof(arr);
size 将得到 40。
你可能会问,为什么前面的 cin 不知道
arr的尺寸,而sizeof却可以得知?因为sizeof事实上是在编译阶段就进行计算。
得知数组的尺寸的一个最大的用处,就是可以在程序里通过计算得到这个数组包含几个元素。这相当于一道小学算术题:
已知某个数组占用40个字节的空间,并且知道该数组单个元素占用4个字节的空间,请问这个数组包含多少个元素?
int arr[] = {1,2,3,4,5,6,7,8,9,0};
//计算arr数组内有多少个元素:
int count = sizeof(arr) / sizeof(arr[0]);
count 将得到10,即arr数组中元素的个数。
其中 sizeof(arr) 得到
整个数组占用的字节数,sizeof(arr[0])
得到单个元素占用的字节数。数组的基本概念就是每一个元素的大小是并且必须一致的(你可以推想以后我们会学到内中元素可以大小不一的数据结构),为什么我们要取第1个(下标为0)元素?
看看下例:
int arr[] = {0};
int count = sizeof(arr) / sizeof(arr[0]);
明白了吗?我们或许不知道或arr有几个元素,但有一点,并且只有一点可以确定:只要是数组,那么它就至少会有一个元素。可以有
int arr[1];但不允许有:int arr[0]。
最后,对于简单的数据类型,sizeof还可以对其类型进行求值:
int size = sizeof(a); size 将得到4
而直接对 int 操作:
int size = sizeof(int); size也将得到4.
数组没有和后者对应的做法。虽然数组也可以有不同类型:整型数组,实型(浮点型)数组,布尔型数组,字符型数组,但数组的大小不仅和它的类型有关系,而且和它含有几个元素有关系。以公式表达:
数组的大小 =& 数组类型的大小 *
元素个数。
sizeof(数组) = sizeof(数组类型)
* 元素个数
sizeof(数组)= sizeof(数组[0])
* 元素个数
字符数组具有一些特殊性,主要在三方面:1、初始化,2、输入输出、3、结束字符。大家需要注意了。
字符数组通常用于显示
前面我们用整型表示成绩,所以数组:int cj[5000];是一个整数数组,或整型数组。
其它的数据类型,如果浮点型:float、布尔型:bool
或者字符型 char 均可以有相关的数组,如:
int age[] {25,32,29,40};
float money[] = {0,2870.95};
bool& married[] = {false,true};
char name[] = {'M','i','k','e'};
最后一行定义了一个字符数组,用来存储一个英文人名:“Mike”。相对于其它数据类型的数组而言,我们将更有需要将字符数组输出。比如上面的name,如何将“Mike”输出到电脑屏幕呢?依据上一小节的知识,应该是:
char name[] = {'M','i','k','e'};
for(int i=0; i&sizeof(name) / sizeof(char); i++)
&& cout && name[i];
上面代码的屏幕输出是:
假设有一个整型数组:
int age[] {25,32,29,40};
当我们要输出它时,大抵应是希望输出如:
25 32 29 40
&或者加上逗号:
<span style="background-color: #,32,29,40
或者干脆换行:
<span style="background-color: #
<span style="background-color: #
<span style="background-color: #
<span style="background-color: #
但对于字符串数组,我们更多地希望它如上面“Mike”一样连续输出。C,C++
设计者们看到了这一点,所以针对字符数组,有以下特殊做法。说是特殊,是指其它类型的数组没有,对于人的习惯来说,它倒是非常“自然”的做法。
字符数组初始化
首先是初始化部分,字符数组允许这样实现:
char name[] = &Mike&;
对于中国人来说,这是一个“救命”的做法,我们不用去“拆”汉字了——还记得吗?一个汉字占两个字节,即一个汉字其实是由两个字符组成的。
char myname[] = &南郁&;
当然,明确指定元素个数也不违法:
char name[5] = &Mike&;
为什么我指定 name 的元素个数为5?
这是一件更重要的问题。后面会说到。
另外,如果你习惯于给数组加花括号,也可以:
char name[] = {&Mike&};
当以后讲到二维数组,花括号就是必须的了。
字符数组的输入
然后是输入:
char name[4];
cout && &请输入您的姓名,不要超过4个英文字符或2个汉字!&
这是运行的结果:
每一个(正直 &&
认真学习)的学员都会指出老师的自相矛盾:前面刚刚说,当“”,即:cin
不知道 name 的大小!
事实上 cin 仍然不知道 name
的大小。所以上段代码一开始就“请求”用户只输入4个以内的字符。用户当然不会那么听话!如果他输入5个,或更多字符,会发生什么糟糕的事情?如果你试试,你可能会发现一切正常,但其实性质严重的错误确实存在。就像一个小孩偷了家里的东西,他倒不会因此被派出所抓走(因为大多数家庭会选择内部处理),但错误的严重性值每个父母重视。
有没有办法让 cin
指接受指定个数的字符?而不管具有“破坏欲望”的用户输入多长?
(本来, cin, cout 这一类仅在
DOS或控制台下或UNIX或Linux下的字符界面下才能用的东东,都不是我们的学习目标。记住,我们只是为了学习C,C++基础知识上的方便,才染指。在讲如何实现前述要求之前,我们来插一个“广告”吧
:)放松放松。
想像南郁老师是电视里的那个漂亮的幼儿园大班女阿姨……
大家——想像时光倒流,回到无忧无虑的童年时代,脸上露出天真无邪的笑容——为了配合场景,还得想像人手一张花花绿绿的盗版Windows XP
安装盘,用左手拿,举着,摆好POS。
好了,本阿姨问:“我们的目标是~~~”。
大家齐声回答:“Windows 编程!!”
呵呵,回答“没有弯路”也许更像一点。 Windows编程的课程即将出笼。。。一开始将都是免费的,暂不收费。但是若要为了“没有弯路“,《白话
C++ 》一定要学完学好。所以你若没有C++基础,建议不要放弃而跑到去学习《白话 Windows编程》。)
(下面这一段是选学内容)
cin.getline的用法:
getline 是一个函数,它可以接受用户的输入的字符,直到已达指定个数,或者用户输入了特定的字符。
它的函数声明形式(函数原型)如下:
istream& getline(char line[], int size, char endchar = &#39;\n&#39;);
不用管它的返回类型,来关心它的三个参数:
char line[]:
就是一个字符数组,用户输入的内容将存入在该数组内。
int size : 最多接受几个字符?用户超过size的输入都将不被接受。
char endchar :当用户输入endchar指定的字符时,自动结束。默认是回车符。
结合后两个参数,getline可以方便地实现:
用户最多输入指定个数的字符,如果超过,则仅指定个数的前面字符有效,如果没有超过,则用户可以通过回车来结束输入。
char name[4];
cin.getline(name,4,&#39;\n&#39;);
由于 endchar 默认已经是 &#39;\n&#39;,所以后面那行也可以写成:
cin.getline(name,4);
如果你想输入三个汉字,把 name
改成6即可。作为配合,你可以用一个宏来指定name的元素个数,也可以用前面的sizeof的方法:
char name[6];
cin.getine(name,sizeof(name));
字符数组的输出
和输入对应,字符数组也允许不用循环,直接用 cout
char name[] = &Mike&;
上面代码的屏幕输出是:
。效果和前面使用 for循环的代码完全一样。
同样的问题是,cout 怎么会知道
name 内有四个字符呢?
为了加深你对这个问题的理解,先来看下面的程序:
char name[] = {&#39;M&#39;,&#39;i&#39;,&#39;k&#39;,&#39;e&#39;};& //我们用“老”的方法初始
运行结果是:
在“Mike”后面,接了一个怪字符。为什么?因为 cout
不知道 name 中有几个字符,结果出输出过头了。你的机器上可能“Mike”之后不是这个怪字符,它是随机不定的。
或许这样的结果还是不足于让你“感动”。下面这个例子绝对值得你去一试。我写完整一点:
#include &iostream.h&
char name[] = {&#39;M&#39;,&#39;i&#39;,&#39;k&#39;,&#39;e&#39;};
char othername[] = &Tom&;
int main(int argc, char* argv[])
& cout && name &&
& cin.get();
& return 0;
运行结果是:
在内存里,由于数组:othername 紧挨着
name 后面,而cout 在输出name时,它并不知道
name中有几个元素,所以把othername 的内容也当成是name的,一并输出了。这应该是一个明明白白的错误表现了。
注意,如果你想把name和othername的定义放在
main 函数内,那为了达到上面的效果,你应把二者的次序倒过来:
int main(int argc, char* argv[])
& char othername[] = &Tom&;&&&&&&
//改成它先定义。
& char name[] = {&#39;M&#39;,&#39;i&#39;,&#39;k&#39;,&#39;e&#39;};
& cout && name &&
& cin.get();
& return 0;
这是因为函数内的局部变量是建立在栈内存,而栈内存是“倒立”的。
如何让 cout 知道 它要输出的字符数组有几个字符?并不是依靠
cout 提供对应于 getline()的函数,而是由
C++ 内建机制,提供一个间接方法来保证程序有办法断定一个字符数组的结束。这就是:为字符数组添加一个空字符为结束。
先来看答案:
char name[] = &Mike&;
就是这么简单,奥妙在于初始化 name
时,请采用这种“特殊”而又“自然”的方法:直接给出用双引号括起来的字符串。当你用这种方法初始时,编译器在编译过程中,将偷偷地为在这个字符数组后面加上一个元素。这个元素就是空字符。
(什么叫空字符?也称“零字符”就是值为0的字符,表示为:&#39;\0&#39;
如果忘了,请复习第五章。)
只有使用 &Mike&
这种方法,编译器才会做这件附加的工作。采用 {&#39;M&#39;,&#39;i&#39;,&#39;k&#39;,&#39;e&#39;}这种方法时,空字符不会被添加。
添加了空字符,cout 就能知道 name
有多长吗?其实它仍然不知,但它不管,它只需一个一个字符地输出,直到碰上空字符,即:&#39;\0&#39;时,它就认为这个字符串结束了。
在 char name[] = &Mike&;
中,编译器真的往 name 后加一个空字符了吗?
我们用两个办法来验证。
第一、通过调试工具观察
眼见为实。字符数组最后的空字符仅用于表示当前字符串结束了,本身并不输出(即使输出,也是一个不占位置的字符,看不到)。但是当程序运行时,它在内存是的的确确占了一个字节,我们可以通过调试来查看。
新建一个控制台工程,添加以下黑体部分代码:
#include &iostream.h&
int main(int argc, char* argv[])
&& char name1[] = {&#39;M&#39;,&#39;i&#39;,&#39;k&#39;,&#39;e&#39;};
&& char name2[] = &Mike&;
&& cout && name1 &&
&& cout && name2 &&
&& return 0;
在其中的某行 cout && ...
上设置断点,然后运行,程序停在断点上,如下图:
将输入鼠标分别在移到代码中的 name1和name2
上,按Ctrl键,并用鼠标点击,将出现Debug Inspector窗口(调试查看器)。
可以清楚看到,name2 多了一个元素,并且值正是:
&#39;\0&#39;。它就是编译器自动加入字符串结束标起:空字符。
第二、既然加了一个字符,那么,name[]
的大小就应该是5,而非4。为了加深记忆,我们用程序验证。
把上面的相关代码做点改动:
char name1[] = {&#39;M&#39;,&#39;i&#39;,&#39;k&#39;,&#39;e&#39;};
char name2[] = &Mike&;
cout && sizeof(name1) &&
cout && sizeof(name2) &&
<span style="background-color: #
正如我们所想,第一种初始化方法,会让 name1的尺寸多出一个字节,那个字节用于存入空字符。
由于引出一个重要的注意事项:指定字符数组的大小时,请注意为空字符预留一个字节。
下面是一个反例:
char name[4] = &Mike&;
由于你强制限定name为4个元素,所以编译器爱莫能助,无法为它加上空字符作为结尾。当cout仍然无法正确地输出。
正确的是:
char name[5] = &Mike&;
最不会犯错的不写:
char name[] = &Mike&;
反过来,当采用 {&#39;M&#39;,&#39;i&#39;,&#39;k&#39;,&#39;e&#39;}
的形式初始化时,我们可以手工添加一个空字符:
char name [] = {&#39;M&#39;,&#39;i&#39;,&#39;k&#39;,&#39;e&#39;,&#39;\0&#39;};
由于有了&#39;\0&#39;作为结束,所以cout可以正确地输出name。
请回答,下面的代码运行后输出应为?
char name [] = {&#39;M&#39;,&#39;i&#39;,&#39;k&#39;,&#39;\0&#39;,&#39;e&#39;};
数组应用实例
我们将举几个例子,例子从简单到复杂。要求大家每个都照课程做一遍。我们不给出源代码。
以下例子均需要包含 #include &iostream.h&。若另需其它头文件,我们在例子中指出。
“小王成绩管理系统新版”
请自定义一个成绩数组(这回使用float类型),并求出其总分和平均分
//为了不重复太多内容,我这里直接用一个现成的数组
//你可以把它改回通过用户输入的方法得到成绩。
float cj[] = {85.5,90,100,70,60,45.5,89};
float pjcj,zcj = 0;
//成绩个数:
int cjCount = sizeof(cj) / sizeof(cj[0]);
for(int i=0;i& cjC i++)
&& zcj += cj[i];
pjcj = zcj / cjC
cout && &总分:& && zcj &&
cout && &平均:& && pjcj &&
cin.get();
在作业中,我们要求大家继续完善本例,让它可以变得专业:
1、可以根据平均分,评判本次试卷的出题质量。(每年教委内部都要对高考试卷进行类似的评析)
2、学员输入自已的座号,可以输出他的成线和计算机对他的评价。
3、可以求最高分和最低分。
“!dnalroB evol
请自定义一字符数组,先将其输出,然后逆序输出(不包括空字符)
char str[] = &I love Borland!&;
//正常输出:
cout && str &&
//字符串长度:
int len = sizeof(str) - 1; //问:为什么要减1?
//逆序输出:
for(int i=len-1; i&=0; i--) //问:为什么 i 从
len-1开始?
& cout && str[i];
这是输出结果:
请在完成本例后,继续尝试这一要求:
如果要求使用这样的for循环:
for(int i=0; i& i++)
&& //?????
仍然要实现逆序输出,请问循环体的那行代码该如何写?这也是我们本章一道作业。
“数组中的玄机”
这个有点意思!
有一整型数组:
int flags [] = {
&& 0,0,0,0,1,1,3,4,6,5,3,3,5,5,6,4,3,2,2,-1,
&& 0,0,0,0,0,2,0,0,0,6,0,0,0,0,6,0,0,1,-1,
&& 0,0,0,0,7,0,0,0,1,10,0,13,0,10,2,0,0,0,8,-1,
&& 0,0,0,0,8,0,0,0,0,1,0,0,0,2,0,0,0,0,7,-1,
&& 0,0,0,0,13,4,6,9,9,13,4,14,4,13,9,9,6,4,13,-1,
&& 0,0,7,3,11,12,4,12,4,12,96,9,12,4,12,4,12,11,3,8,-1,
&& 0,0,8,13,2,2,0,0,0,0,4,12,4,0,0,0,0,1,1,13,7,-1,
&& 0,0,0,0,12,9,0,0,15,0,15,0,15,0,0,0,12,-1,
&& 0,0,0,0,4,12,9,0,0,16,17,18,19,0,9,12,4,-1,
&& 0,0,0,0,7,0,4,12,9,0,0,0,0,0,0,9,12,4,0,8,-1,
&& 0,0,0,13,8,0,0,8,4,12,9,0,9,12,4,7,0,0,7,13,-1,
&& 0,0,1,1,1,13,13,13,2,0,4,12,4,0,1,13,13,13,2,-1,
请根据该数组中各元素的值依次做如下输出:
3 =& `&& 在数字键 1
4 =& &#39;& 单引号
5 =& && 双引号
6 =& -& 连字符
10 =& o 小写字母 o
11 =& ; 分号
13 =& _ 下划线
15 =& ☆、16
=& 我、17 =& 爱、18
注意除了 15,16,17,18
为汉字以外,其它均为英文状态下输入字符。而当输出汉字时,记得使用 &&,因为一个汉字相当于个字符,所以应算字符串。另外:单引号、双引号、反斜杠
需要使用转义字符,详见下面代码。
看看输出结果是什么?其实这是我们小时候的游戏啦。一张看似乱七八糟的图,旁边写着:“请小朋友把图中带点的小格子涂上红颜色,看看图中隐藏着什么?”——呵呵,童年真好。快打开CB,让我们一起回到童年。
这是代码:
int flags [] = {
从上面拷贝数组元素的值*/
int flagCount = sizeof(flags) / sizeof(flags[0]);
for(int i=0;i&flagCi++)
&& switch(flags[i])
&&&&& case 0 : cout && &#39; &#39;;&&
&&&&& case 1 : cout && &#39;(&#39;;
&&&&& case 2 : cout && &#39;)&#39;;
&&&&& case 3 : cout && &#39;`&#39;;&&
//数字键 1 左边的字符
&&&&& case 4 : cout && &#39;\&#39;&#39;;
&&&&& case 5 : cout && &#39;\&&#39;;&
&&&&& case 6 : cout && &#39;-&#39;;
& //连字符
&&&&& case 7 : cout && &#39;/&#39;;&&
&&&&& case 8 : cout && &#39;\\&#39;;&
&&&&& case 9 : cout && &#39;.&#39;;
&&&&& case 10 : cout && &#39;o&#39;;
&//小写字母 o
&&&&& case 11 : cout && &#39;;&#39;;
&&&&& case 12 : cout && &#39;#&#39;;
&&&&& case 13 : cout && &#39;_&#39;;& //下划线
&&&&& case 14 : cout && &#39;=&#39;;
&&&&& case 15 : cout && &☆&; //汉字字符注意要用双引号
&&&&& case 16 : cout && &我&;
&&&&& case 17 : cout && &爱&;
&&&&& case 18 : cout && &你&;
&&&&& case 19 : cout && &#39;!&#39;;
&&&&& case -1 : cout && //换行
(本例全部源代码请查看课程例子包)
虽说是嚷嚷着要回到童年,可以看到输出结果后,我怎么觉得它有些“少儿不宜”呢?如果你有女朋友,把这个程序寄给她吧。她一定会很开心。
如果不依靠数组事先定义好的图形数据,要纯粹使用流程控制来输出上面的图形,将会很困难。本例演示了“流程”+“数据”之间的配合,从面更直观地实现程序的目的。
“猜奖Ver 1.0”
定义一含10元素的整型数组,通过随机函数,让每个元素值为随机的一个2位数(10~99)。然后要求用户输入一个数,如果该数在数组内,则打印出该数及其所在的数组下标,有几个打几个。
最后奖10个随机数输出到屏幕。
分析:简要地说,这是一个在数组中查找某数问题。
随机数用 random()
函数来实现,它的原型(或声明)为:
int random(int &num);
(所谓随机值,即值的大小不定,就像体育彩票开号时的“在气箱里飘浮的乒乓球”,或者麻将开局时往桌上一扔的那个家伙,都可以得出的一个事先不能确定大小的数
random 可以返回一个随机整数,这个整数的范围在 0 ~ num-1。
所以若有 int a = random(100); 则a是
0~99 之间的一个数。记住了,是0~num-1,而不是1~num。
题目要求是随机的一个2位数(10~99),所以应这么写: random(89)+ 10
另外,为了让电脑能准确地得到随机数,必须在之前至少执行过一次另一个函数:随机种子函数:randomize();它没有参数没有返回值。只需拿来用就是。当然,这两个函数的声明包含在
stdlib.h 文件内。所以我们得包含这个头文件。
再一次碰到随机函数,我就不这么详细它们了。
#include &iostream.h& //怕你忘了,还是写上吧
#include &stdlib.h&
int main(int argc, char* argv[])
&& #define MAX_NUM 10
&& int num[MAX_NUM];
&& //随机种子:
&& randomize();
&& //得到随机数:
&& for(int i=0;i&MAX_NUM;i++)
&&&&& num[i] =
random(89) + 10;
&& //用户输入一个数:
&& cout && &请输入一个2位数,试试您是否能中奖:&;
&& //在随机数组中查找a:
&& int count = 0; //找到的次数
&& for(int i=0;i&MAX_NUM;i++)
if(a == num[i])
&& cout && i && &==&& && num[i]
&& //输出一条横线,仅为美观:
&& char line[] =
&----------------------------&;
&& cout && line &&
&&&&& //找到0次,说明没有猜中:
&& if(count == 0)
cout && &谢谢使用,欢迎下次再来!& &&
cout && &恭喜!您中了& && count && &次& &&
&& //最后显示所有随机数:
&& cout && line &&
&& for(int i=0; i& MAX_NUM; i++)
cout && num[i] &&
&& cout && line &&
&&&&& //由于前面有输入,所以需要两行get();
&& cin.get();
&& cin.get();
&& return 0;
这是我的一次运行结果:
运气不佳,我连猜20次也没中一次,本来想写完课程去买体彩的,想想还是算了,500万可要比这个还难得多!这个例子我们就称为“猜奖V1.0”吧,以后让我们慢慢升级到中奖概率和体彩一样的版本。
本题中奖的机率是多少呢? 用户有90种数值可选(10~89),而每次可能中奖的数值为10个。所以中奖机率为
10 / 90 。计算百分比为: 10 * 100 / 90 = 11.11 %。
在作业中,我们布置一条题目,要求大家改变本例代码,使它可以验证中奖机率。
最值指最大值和最小值。我们可以写程序,经过一遍循环,同时求出这两个值。
为了不让事情一下复杂化,所我们先来求最大值,然后再加入求最小值的代码(这也是写程序中极其常见的一种技巧)。
我不写思路,请大家自已分析代码。
#define MAX_NUM 10
int num[MAX_NUM] = {45,34,56,78,12,56,70,67,2,76};
int maxV //最大值
maxValue = num[0]; //假设最大值是第一个元素。
//然后让它和第二个、第三个……一个个比下去:
for(int i = 1; i&MAX_NUM; i++)
&& //如果后面有个元素,比当前“假设”的最大值还要大,那就把最大值换作是它:
&& if ( num[i] & maxValue)
&&&&&& maxValue = num[i];
cout && &最大值:& &&maxV
现在我们加入最小值的查找:
int maxValue,minV //最大值、最小值
maxValue = minValue = num[0]; //假设最大值和最小值都是第一个元素。
//然后让它们和第二个、第三个……一个个比下去:
for(int i = 1; i&MAX_NUM; i++)
&& //如果后面有个元素,比当前临时的最大值还要大,那就把最大值换作是它:
&& if (num[i] & maxValue)
&&&&&& maxValue = num[i];
&& //如果后面有个元素,比当前临时的最小值还要小,那就把最小值换作是它:
&& if (num[i] & minValue)
&&&&&& minValue = num[i];
cout && &最大值:& &&maxValue &&
cout && &最小值:& &&minValue &&
一点优化。
在 for() 循环中,每次都要作两个比较:1、比较
num[i] 是否比 maxValue还大,1、比较
num[i] 是否比 minValue还小。仔细一想:如果
num[i] 比 最大值还要大,那它就不可能比
最小值还小,对不? 反过来说,如果num[i]想比minValue还小,那么一定是在它不比
maxValue 大的情况下。
代码优化为:
for(int i = 1; i&MAX_NUM; i++)
&& if (num[i] & maxValue) //如果后面有个元素,比当前临时的最大值还要大,那就把最大值换作是它:
&&&&&& maxValue = num[i];&&
&& else if (num[i] & minValue) //如果后面有个元素,比当前临时的最小值还要小,那就把最小值换作是它:
&&&&&& minValue = num[i];
小结:我们又迈出了重要的一步
奢谈我们又如何迈出如何重要的一步是没有意义的。在小结本章之前,你应该把本章所有能上机演练的代码都调通一遍。其实这章的例子都有很意思,对不——当初我们学了“流程控制”时,我们说我们的例子会精彩一点,而当我们开始了“数组”的学习,我们的例子会更有意思。为什么?
有个说法:“程序 = 算法 +
数据结构”。我们可以把写程序理解为雕刻,要想有好作品,除了你的手艺要高以外,同样重要的是要有一个好的原材,如果没有,正所谓“朽木不可雕也”。在这个角度上看,世间所有创作皆同此理。
另外,我们也可以把“算法”称为“对动作的表达”;而把“数据结构”称为“对数据的表达”。
因此——说点题外话——当你想表达世间最真挚的感情时,最好作最本质的“动作”和“数据”表达。比如,真爱一个人,就对他说“我爱你”;而不要说成“我非常爱你”,除非同样的爱你还有好几份,只不过这一份是“非常的”,而另一份可能是“不那么非常的”,加了比较爱就俗了——更不要学腔“我好爱好爱你啊~~~~~”,这不仅“俗”,而且是“媚俗”。
所以程序员应该这样对他的爱人表达:“真爱的算法,只能用在惟一的数据结构上,那就是:你;来吧,让我们结婚吧,我相信我的算法加上你的数据结构,一定会演算出世间最美好的爱情………”
说点正经话。 类似:
这样C++基本的数据,谈不上有什么数据结构:只有一个数据,也就没有组合;没有组合,又何来结构呢?如果非要说是一种结构,那么就称它们为“单一结构”吧。当然,在C++中,最小可处理的内存单位为字节。一个整型由4个字节组成,从这点上看似乎单一的整数变量也有结构,不过个这个结构其实由计算机内部决定并实现,不在我们操作范围。
而数组,则是我们接触到的第一种具有“复合结构”(相对于前面的“单一结构”)的数据。记住,在这里学习,你首先要把数组理解为一种“数据结构”,然后才能像其它教科书一样把它理解为“数据类型”。
不算“单一结构”。则数组是最简单的数据结构。它的特点是:
1、各元素在内存中的大小都一样,因为所有元素只能是同一种数据类型;
2、各元素在内存中整齐划一地连续排列。
正因为有这些特点,我们才可以方便通过数组的下标来操作数组中指定元素。下标从0开始,则下标为 n
的元素,就是数组中第 n+1
个元素。想想看,一群人排成一条整齐的队伍,我们就可以方便地说第7个是谁,第100个又是谁;如果这群人一盘散沙地凑在一起,那谁是第7个,谁又是第100个呢?
这就叫做:“数据结构决定了对数据的存取方法”。
先看看这个数组内存示意图吧,下一章我们会先专门讲数组在内存是如何排列的。
从图中你看出了数组中各元素在内存位置的关系了吗:1、大小一致;2、连续排列。比“队伍”更好联想是一排抽屉。
下一章我们在数组内存结构的基础上,讲字符串、二维数组、字符串数组等。

我要回帖

更多关于 ios 字符串 单个字符 的文章

 

随机推荐