编写一个c语言程序,统计一个班(最多50人)的学生成绩(在vc下编写)

weivs929 2008-06-23 10:17:45
刚开始学这个 ,有很多地方不懂 望高手赐教!
1.由键盘输入每个学生的学号。姓名和四门成绩
2.计算每个学生的平均分和总分
3.按部分排序(从高到底)并按名次输出
4.根据要求输出某门功课(由键盘输入课程号)成绩在90分以上(包括90分)的学生概况
具体要求
1,每个功能为一个独立的函数
2.所有的键盘输入要求提示
...全文
2944 15 打赏 收藏 转发到动态 举报
写回复
用AI写文章
15 条回复
切换为时间正序
请发表友善的回复…
发表回复
heimazhanghui 2008-10-30
  • 打赏
  • 举报
回复
谢谢你们了,我正要找这个程序!
tengye19840704 2008-09-12
  • 打赏
  • 举报
回复
五楼的 char zifu[][5]={"学号","12345","?文","数学 ","英?"};
这里每一项为什么只能输入五个字符啊..多输就错了.例如上面那样.
congzai1 2008-09-12
  • 打赏
  • 举报
回复
又是课后作业拿来问。。。。。。中国以后的程序员咋办呢。。。。。。。
我看你有戏 2008-06-30
  • 打赏
  • 举报
回复

看看这个源代码

http://60.191.231.66:8105/ShowFileData.aspx?ID=323

http://60.191.231.66:8105/ShowFileData.aspx?ID=323
kgduwu 2008-06-29
  • 打赏
  • 举报
回复
restart;
}

}
}
temp = loadp;
loadp->next = p;
loadp = p;
if(p->next == NULL)
{
printf("装载完成,共装载%d个学生记录!\n",count);
goto end;
}
else
{
loadp->next = NULL;
goto restart;
}

}
}
else
{
free(p);
if(count == 0)
{
printf("文件数据错误,装载失败!\n");
goto end;
}
else
{
printf("已装载%d个学生记录,读取该文件第%d个学生记录时数据错误,程序返回!\n",count,(count + 1));
goto end;
}
}
}
}
end:
if(head == NULL)
return(loadhead);
else
{
pn = head;
while(pn->next)
{
pn = pn->next;
}
pn->next = loadhead;
return(head);
}
}

float scorceinput(void)
{ float a = 0;
bool scorceflag = false;
while(!scorceflag)
{
scanf("%f",&a);
scorceflag = scorcecheck(a);
if(scorceflag)
return(a);
else
printf("输入有误(分值必须介于0 ~100之间),请重新输入: ");
}
}

bool scorcecheck(float a)
{
if((a >= 0)&&(a <= 100))
return(true);
else
return(false);
}

struct student * nodedel(struct student *delnode,struct student *head)
{
struct student *p;
struct student *pnext;
p = head;
pnext = head->next;
if(head == delnode)
head = head->next;
else
{while(pnext != delnode)
{
p = pnext;
pnext = pnext->next;
}
p->next = delnode->next;
}
free(delnode);
return head;
}
这是恰好以前写好的,有源码,编译通过可运行的,太长,复制时注意每一楼的连接处,代码有错位,你给出你的邮箱也可发给你,供参考
buerke 2008-06-28
  • 打赏
  • 举报
回复
高手就是高手
这么不一样
kgduwu 2008-06-28
  • 打赏
  • 举报
回复
bool save(struct student *head)
{
FILE *fp;
struct student *p;
char filename[10];
int count = 0;
p = head;
if(p == NULL)
{
printf("该文件为空,保存出错!\n ");
return(false);
}
printf("请输入你要保存的文件名(8字节以内): ");
scanf("%s",filename);
if((fp = fopen(filename,"wb")) == NULL)
{
printf("文件指针未能打开,保存失败!\n");
return(false);
}
else
{
while(p)
{
fwrite(p,sizeof(struct student),1,fp);
count = ++count;
p = p->next;
}
printf("共要保存 %d 个的学生的信息!\n",count);
fclose(fp);
return(true);
}

}

struct student * load(struct student *head)
{
FILE *fp;
struct student *p;
char filename[16],flag = 'n';
bool saveflag = false;
p = head;
if(p != NULL)
{
printf("内存中当前学生信息不为空,尾部附加文件装载失败或清空当前文件重新装载都将丢失当前学生信息,是否先备份当前学生信息到磁盘?( y/n : ?) : \n");
fflush(stdin);
flag = getchar();
if(flag == 'y')
{
saveflag = save(p);
if(saveflag)
printf("保存完成,请继续!\n");
}
reinput:
fflush(stdin);
printf("请输入你要装载的文件名(8字节以内): ");
scanf("%s",filename);
if((fp = fopen(filename,"rb")) == NULL)
{
printf("无法打开该文件,文件不存在或文件错误,重新输入(请按y),放弃装载(请按n或其它键)!y/n? : \n");
fflush(stdin);
flag = getchar();
if(flag == 'y')
{
goto reinput;
}
else
{
printf("无法打开该文件,你已放弃装载!\n");
return(head);
}
}
printf("装载该文件到当前文件尾部(请按y),清空内存中学生信息重新装载(请按l),放弃(请按a或其它键)( y/l/a : ?)? : \n");
// printf("是否打算继续输入?是/否(y/n)?: ");
fflush(stdin);
flag = getchar();
if(flag == 'y')
{
printf("装载文件到当前文件尾部请稍侯......\n");
}
else if(flag == 'l')
{
freelikst(head);
head = NULL;
printf("已清空当前内存文件重新装载请稍侯......\n");
}
else
{
printf("你已放弃装载!\n");
fclose(fp);
return(head);
}
}
else
{
reinput1:
fflush(stdin);
printf("请输入你要装载的文件名(8字节以内): ");
scanf("%s",filename);
if((fp = fopen(filename,"rb")) == NULL)
{
printf("无法打开该文件,文件不存在或文件错误,重新输入(请按y),放弃装载(请按n或其它键)!y/n? : \n");
fflush(stdin);
flag = getchar();
if(flag == 'y')
{
goto reinput1;
}
else
{
printf("无法打开该文件,你已放弃装载!\n");
return(head);
}
}
}
head = readcheck(head,fp);
fclose(fp);
return(head);
}
bool freelikst(struct student *head)
{
// bool saveflag = false;
struct student *p;
struct student *pnext;
p = head;
pnext = NULL;
if(head->next == NULL)
free(p);
else
{
pnext = head->next;
while(pnext != NULL)
{
free(p);
p = pnext;
pnext = pnext->next;
}
}
printf("旧链表释放成功!\n");
return(true);
}

struct student * readcheck(struct student *head,FILE *fp)
{
struct student *p;
struct student *pn;
struct student *loadp;
struct student *loadhead;
struct student *checknode;
struct student *temp;
p = NULL;
pn = NULL;
loadhead = NULL;
checknode = NULL;
loadp = NULL;
temp = NULL;
int sele = 2;
int count = 0;
while(true)
{
restart:
p = (struct student *)malloc(sizeof(struct student));
if(p == NULL)
{
printf("读取文件内存申请失败,装载失败,程序退出!\n");
exit(1);
}
if(fread(p,sizeof(struct student),1,fp) != 1)
{
free(p);
if(feof(fp))
{ if(count)
printf("文件读取结束,学生记录为空!\n");
else
printf("文件读取结束,共装载%d个学生记录!\n",count);
goto end;
}
else
{
if(count)
printf("文件损坏或文件格式不是程序支持的本文件格式!\n");
else
printf("文件后部损坏,已装载%d个学生记录!\n",count);
goto end;
}
}
else
{
if((p->stu_no != NULL)&&(scorcecheck(p->sco.chin))&&
(scorcecheck(p->sco.math))&&(scorcecheck(p->sco.engl))&&
(scorcecheck(p->aver))&&((((p->aver) - (avera((float)(p->sco.chin)
,(float)(p->sco.math),(float)(p->sco.engl))) >= -0.0001))||(((p->aver) - (avera((float)(p->sco.chin)
,(float)(p->sco.math),(float)(p->sco.engl))) <= 0.0001))))
{
count = ++count;
if(head)
{
checknode = check(p->stu_no,head);
if(checknode != NULL)
{
printf("磁盘中读取到的学生记录与内存中的学生记录学号重复:\n");
printf("内存中的学生记录为:\n");
printf("学号\t姓名\t语文\t\t数学\t\t英语\t\t平均成绩\n");
printf("%d\t%s\t%f\t%f\t%f\t%f\n",checknode->stu_no,checknode->stu_name,
checknode->sco.chin,checknode->sco.math,checknode->sco.engl,
checknode->aver);
printf("磁盘中读取到的学生记录为:\n");
printf("学号\t姓名\t语文\t\t数学\t\t英语\t\t平均成绩\n");
printf("%d\t%s\t%f\t%f\t%f\t%f\n",p->stu_no,p->stu_name,
p->sco.chin,p->sco.math,p->sco.engl,p->aver);
printf("请选择删除内存中的学生记录或丢弃磁盘中学生记录:[1,2]\n");
printf("删除内存中的该学生记录:请选:1\n");
printf("放弃磁盘中学生记录:请选:2\n");
do
{ printf("你的选择是:");
fflush(stdin);
scanf("%d",&sele);
if(!((sele == 1)||(sele == 2)))
printf("选择有误,请重新输入\n");
}
while(!((sele == 1)||(sele == 2)));
switch(sele)
{
case 1:
head = nodedel(checknode,head);
printf("内存中的该学生记录已被删除!\n");
break;
case 2:
count = --count;
printf("从磁盘中读取到的该学生记录信息已被丢弃!\n");
if(p->next == NULL)
{
if(count ==0)
{
printf("装载完成,没有学生记录被装载到内存!\n");
}
else
{
printf("装载完成,共装载%d个学生记录!\n",count);
}

free(p);
goto end;
}
else
{
free(p);
goto restart;
}

}
}
}

if(count ==1)
{
dkonere:
loadhead = p;
loadp = p;
temp = p;
if(p->next == NULL)
{
loadhead->next = NULL;
printf("装载完成,共装载%d个学生记录!\n",count);
goto end;
}
else
{
loadp->next = NULL;
goto restart;
}

}
else
{
checknode = check(p->stu_no,loadhead);
if(checknode != NULL)
{
printf("磁盘中现读取到的学生记录与磁盘中已读取到的学生记录学号重复:\n");
printf("磁盘中已读取到的学生记录为:\n");
printf("学号\t姓名\t语文\t\t数学\t\t英语\t\t平均成绩\n");
printf("%d\t%s\t%f\t%f\t%f\t%f\n",checknode->stu_no,checknode->stu_name,
checknode->sco.chin,checknode->sco.math,checknode->sco.engl,
checknode->aver);
printf("磁盘中现读取到的学生记录为:\n");
printf("学号\t姓名\t语文\t\t数学\t\t英语\t\t平均成绩\n");
printf("%d\t%s\t%f\t%f\t%f\t%f\n",p->stu_no,p->stu_name,
p->sco.chin,p->sco.math,p->sco.engl,p->aver);
printf("请选择丢弃磁盘中已读取到的学生记录或丢弃磁盘中现读取到的学生记录:[1,2]\n");
printf("丢弃磁盘中已读取到的学生记录:请选:1\n");
printf("丢弃磁盘中现读取到的学生记录:请选:2\n");
do
{ printf("你的选择是:");
fflush(stdin);
scanf("%d",&sele);
if(!((sele == 1)||(sele == 2)))
printf("选择有误,请重新输入\n");
}
while(!((sele == 1)||(sele == 2)));
switch(sele)
{
case 1:
loadhead = nodedel(checknode,loadhead);
count = --count;
printf("磁盘中已读取到的学生记录已被丢弃!\n");
if(count = 1)
goto dkonere;
break;
case 2:
count = --count;
printf("从磁盘中现读取到的该学生记录信息已被丢弃!\n");
if(p->next == NULL)
{

printf("装载完成,共装载%d个学生记录!\n",count);
free(p);
goto end;
}
else
{
free(p);
goto
kgduwu 2008-06-28
  • 打赏
  • 举报
回复
struct student * del(struct student *head)
{
struct student *p;
struct student *pnext;
struct student *checkflag;
int stuno,t;
char delflag,delaskflag,restartflag = 'n',abortflag = 'n';
checkflag = NULL;
p = head;
pnext = head;
if(head == NULL)
{
printf("学生成绩管理信息系统数据为空,没有数据可供删除,请选择其它功能项目!\n");
goto end;
}
else
restart:
do
{
// if(head != NULL)
// {
printf("请输入你要删除的学生学号: ");
fflush(stdin);
scanf("%3d",&stuno);
checkflag = check(stuno,head);
if(checkflag == NULL)
{
printf("你输入的学生学号%3d不存在,重新输入请按y,放弃删除请按其它键!\n",stuno);
fflush(stdin);
abortflag = getchar();
if(abortflag != 'y')
goto end;
}
// }
}while(checkflag == NULL);
printf("你要删除的学号为%3d的学生信息如下:\n",stuno);
printf("学号\t姓名\t语文\t\t数学\t\t英语\t\t平均成绩\n");
printf("%d\t%s\t%f\t%f\t%f\t%f\n",checkflag->stu_no,checkflag->stu_name,checkflag->sco.chin,checkflag->sco.math,checkflag->sco.engl,checkflag->aver);
printf("你确认要删除学号为%3d的学生信息吗?是/否(y/n)?: ",stuno);
fflush(stdin);
delaskflag = getchar();
if(delaskflag != 'y')
{
printf("你是否打算删除其它学号的学生信息吗?是/否(y/n)? :");
fflush(stdin);
restartflag = getchar();
if(restartflag == 'y')
goto restart;
else
goto end;
}
else
{
do
{
if(pnext != checkflag)
{

p = pnext;
pnext = pnext->next;
t = 0;

}
else
{
if(pnext == head)
{
head = pnext->next;
p = pnext->next;
pnext = pnext->next;
t = 1;
if(head == NULL)
{
printf("学号为%3d的学生信息删除完成! \n",stuno);
printf("现学生成绩管理信息系统数据为空,没有数据可供删除,请选择其它功能项目!\n");
goto end;
}
}
else
{
p->next = pnext->next;
t = 1;
p = head;
pnext = head;
}
printf("学号为%3d的学生信息删除完成! \n",stuno);
free(checkflag);
}
}while(t != 1);

printf("你是否还要删除其他学生的信息?是/否(y/n)?: ");
fflush(stdin);
delflag = getchar();
if(delflag == 'y')
goto restart;
end:
return head;
}
}

struct student * edit(struct student *head)
{
struct student *p;
struct student *checkflag;
int stuno;
char name[20];
checkflag = NULL;
p = NULL;
char editflag,editaskflag,restartflag = 'n',abortflag = 'n';
if(head == NULL)
{
printf("学生成绩管理信息系统数据为空,没有数据可供修改,请选择其它功能项目!\n");
goto end;
}
else
restart:
do
{
printf("请输入你要修改的学生学号: ");
fflush(stdin);
scanf("%3d",&stuno);
checkflag = check(stuno,head);
if(checkflag == NULL)
{
printf("你输入的学生学号%3d不存在,重新输入请按y,放弃修改请按其它键!\n",stuno);
fflush(stdin);
abortflag = getchar();
if(abortflag != 'y')
goto end;
}
}while(checkflag == NULL);
p = checkflag;
printf("你要修改的学号为%3d的学生信息如下:\n",stuno);
printf("学号\t姓名\t语文\t\t数学\t\t英语\t\t平均成绩\n");
printf("%d\t%s\t%f\t%f\t%f\t%f\n",checkflag->stu_no,checkflag->stu_name,checkflag->sco.chin,checkflag->sco.math,checkflag->sco.engl,checkflag->aver);
printf("你确认要修改学号为%3d的学生信息吗?是/否(y/n)?: ",stuno);
fflush(stdin);
editaskflag = getchar();
if(editaskflag != 'y')
{
printf("你是否打算修改其它学号的学生信息吗?是/否(y/n)? :");
fflush(stdin);
restartflag = getchar();
if(restartflag == 'y')
goto restart;
else
goto end;
}
fflush(stdin);
printf("请输入你要修改的学号为%3d的学生的姓名:",stuno);
gets(name);
strcpy(p->stu_name,name);
printf("请输入你要修改的学号为%3d的学生的语文成绩:",stuno);
p->sco.chin = scorceinput();
printf("请输入你要修改的学号为%3d的学生的数学成绩:",stuno);
p->sco.math = scorceinput();
printf("请输入你要修改的学号为%3d的学生的英语成绩:",stuno);
p->sco.engl = scorceinput();
p->aver = avera(p->sco.chin,p->sco.math,p->sco.engl);
printf("\n");
printf("你修改后的学号为%3d的学生信息如下:\n",stuno);
printf("学号\t姓名\t语文\t\t数学\t\t英语\t\t平均成绩\n");
printf("%d\t%s\t%f\t%f\t%f\t%f\n",p->stu_no,p->stu_name,p->sco.chin,p->sco.math,p->sco.engl,p->aver);
printf("你是否还要修改其他学生的信息?是/否(y/n)?: ");
fflush(stdin);
editflag = getchar();
if(editflag == 'y')
goto restart;
end:
return head;
}
struct student * sort(struct student *head)
{
struct student *h;
struct student *pnext;
int sele = 0;
h = head;
pnext = head;
if(head == NULL)
{
printf("学生成绩管理信息系统数据为空,请选择其它功能项目!\n");
goto end;
}
printf("请选择排序索引:[2,1,0]\n");
printf("以学号由小到大排序:请选:1\n");
printf("以学生平均成绩由高到低排序:请选:2\n");
printf("放弃排序:请选:0\n");
do
{ printf("你的选择是:");
fflush(stdin);
scanf("%d",&sele);
if(!((sele >= 0)&&(sele <= 2)))
printf("选择有误,请重新输入\n");
}
while(!((sele >= 0)&&(sele <= 2)));
switch(sele)
{
case 0:
goto end;
case 1:
head = nosort(h);
printf("以学号由小到大排序完成!\n");
break;
case 2:
head = aversort(h);
printf("以学生平均成绩由低到高排序完成!\n");
break;
}
end:
return head;

}


float avera(float a,float b,float c)
{
float avera = (float)((a + b + c)/3.0);
return avera;
}

struct student * check(int studentno,struct student *head)
{
struct student *p;
p = head;
while(studentno != p->stu_no)
{
p = p->next;
if(p == NULL)
break;
}
return p;
}
struct student * nosort(struct student *h)
{
struct headnode headbak;
struct student *p,*q,*temp,*temp1,*temp2,*temp3,*temp4;
int relaflag = 0,sortflag = 0,t =0;
headbak.head = h;
p = h;
q = NULL;
if(p != NULL)
{
q = h->next;
}
else
{
q = NULL;
}
if(q == NULL)
{
headbak.head = h;
}
else
{
while(p->next)
{
relaflag = 0;
while(q)
{
if(p->stu_no >= q->stu_no)
t = 1;
else
t = 0;
switch(t)
{
case 1:
{
{
if(relaflag ==0)
{
temp = p;
temp1 = q->next;
p = q;
q = temp;
p->next = temp;
q->next = temp1;

}
else
{
temp = p;
temp1 = q->next;
temp2->next = p;
temp3 = p->next;
p = q;
q = temp;
p->next = temp3;
q->next = temp1;
}
}
}
case 0:
temp2 = q;
q = q->next;
relaflag = (++relaflag);
if(sortflag == 0)
headbak.head = p;

}
}
if(sortflag == 0)
temp4 = p;
else
{
temp4->next = p;
temp4 = temp4->next;
}
p = p->next;
sortflag = (++sortflag);
q = p->next;
}
}
return headbak.head;
}
struct student * aversort(struct student *h)
{
struct headnode headbak;
struct student *p,*q,*temp,*temp1,*temp2,*temp3,*temp4;
int relaflag = 0,sortflag = 0,t =0;
headbak.head = h;
p = h;
q = NULL;
if(p != NULL)
{
q = h->next;
}
else
{
q = NULL;
}
if(q == NULL)
{
headbak.head = h;
}
else
{
while(p->next)
{
relaflag = 0;
while(q)
{
if(p->aver >= q->aver)
t = 1;
else
t = 0;
switch(t)
{
case 1:
{
{
if(relaflag ==0)
{
temp = p;
temp1 = q->next;
p = q;
q = temp;
p->next = temp;
q->next = temp1;

}
else
{
temp = p;
temp1 = q->next;
temp2->next = p;
temp3 = p->next;
p = q;
q = temp;
p->next = temp3;
q->next = temp1;
}
}
}
case 0:
temp2 = q;
q = q->next;
relaflag = (++relaflag);
if(sortflag == 0)
headbak.head = p;

}
}
if(sortflag == 0)
temp4 = p;
else
{
temp4->next = p;
temp4 = temp4->next;
}
p = p->next;
sortflag = (++sortflag);
q = p->next;
}
}
return headbak.head;
}
kgduwu 2008-06-28
  • 打赏
  • 举报
回复
俺也贴一个,只有学号,姓名,语文,数学,英语,平均成绩,进行学号唯一检查与排序,进行平均成绩排序,可增加,修改,统计学生数量,查看全部学生信息,可存盘读盘,读盘时将盘中的记录数据是否正确,并可选择是将盘中的信息读取并附加到内存中的信息表尾部,还是先清除内存中的信息表后直接读取盘中的信息,如果学号重复时可选择保留内存中的记录还是盘中的记录,供参考!
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
struct student * create(struct student *);
int funcsele(void);
struct student * app(struct student *);
struct student * load(struct student *);
struct student * edit(struct student *);
struct student * del(struct student *);
void cout(struct student *);
struct student * print(struct student *);
struct student * sort(struct student *);
struct student * nosort(struct student *);
struct student * aversort(struct student *);
bool save(struct student *);
bool freelikst(struct student *);
float avera(float a,float b,float c);
struct student * check(int studentno,struct student *head);
bool scorcecheck(float a);
float scorceinput(void);
struct student * nodedel(struct student *,struct student *);
struct student * readcheck(struct student *,FILE *);

struct SCORCE
{
float chin;
float math;
float engl;
};
struct student
{
int stu_no;
char stu_name[20];
SCORCE sco;
float aver;
struct student *next;
};

struct headnode
{
struct student *head;
};


void main()
{
int sele;
char creat,ext;
bool fileflag = true;
struct student *h;
h = NULL;
printf("学生成绩管理信息系统初始: 创建(请按c),装载(请按l),放弃退出(请按a)?: ");
creat = getchar();
if(creat == 'c')
h = create(h);
else if(creat == 'l')
h = load(h);
else if(creat == 'a')
{
printf("你已放弃了学生成绩管理信息系统的建立,程序即将退出!");
exit(1);
}
if(h == NULL)
{
printf("学生成绩管理信息系统初始创建或磁盘装载不成功,程序即将退出!");
exit(1);
}
do
{
sele = funcsele();
switch(sele)
{
case 0:
exit(1);
case 1:
h = app(h);
break;
case 2:
h = load(h);
break;
case 3:
h = edit(h);
break;
case 4:
h = del(h);
break;
case 5:
cout(h);
break;
case 6:
h = print(h);
break;
case 7:
h = sort(h);
break;
case 8:
fileflag = save(h);
if(fileflag)
printf("保存完成!\n");
break;
}
fflush(stdin);
printf("是否退出?y/n? : ");
ext = getchar();
if(ext == 'y')
exit(1);
}while(ext != 'y');
exit(1);
}

struct student * create(struct student *head)
{
struct student *checkflag;
struct student *p;
struct student *tail;
char name[20],flag = 'y';
// float average;
// head = NULL;
checkflag = NULL;
p = NULL;
tail = NULL;
while(flag == 'y')
{
p = (struct student *)malloc(sizeof(struct student));
if(p == NULL)
{
printf("内存申请失败!\n");
exit(1);
}
else
{
do
{
printf("请输入学生学号:");
fflush(stdin);
scanf("%3d",&(p->stu_no));
if(head != NULL)
{
checkflag = check(p->stu_no,head);
if(checkflag != NULL)
printf("学生学号重复,请重新输入!\n");
}
}while(checkflag != NULL);
fflush(stdin);
// printf("学生学号:%d",p->stu_no);
printf("请输入学生姓名:");
gets(name);
// printf("\n学生姓名:%s",name);
strcpy(p->stu_name,name);
printf("请输入学生语文成绩:");
p->sco.chin = scorceinput();
printf("请输入学生数学成绩:");
p->sco.math = scorceinput();
printf("请输入学生英语成绩:");
p->sco.engl = scorceinput();
p->aver = avera(p->sco.chin,p->sco.math,p->sco.engl);
printf("\n");
printf("学号\t姓名\t语文\t\t数学\t\t英语\t\t平均成绩\n");
printf("%d\t%s\t%f\t%f\t%f\t%f\n",p->stu_no,p->stu_name,p->sco.chin,p->sco.math,p->sco.engl,p->aver);
if(head == NULL)
{
head = p;
tail = p;
tail->next = NULL;
}
else
{
tail->next = p;
tail = p;
tail->next = NULL;
}
}
printf("是否打算继续输入?是/否(y/n)?: ");
fflush(stdin);
flag = getchar();
}
if(head != NULL)
{
// tail->next = NULL;
printf("系统初始创建结束\n");
}
return head;
}

int funcsele()
{
int func = 1;
printf("\n学生成绩管理功能项目选择:[1,2,3,4,5,6,7,8,0]\n");
printf("追加学生信息至表尾:请选:1\n");
printf("从磁盘装载一个学生信息表至内存:请选:2\n");
printf("修改表中学生信息:请选:3\n");
printf("删除表中学生信息:请选:4\n");
printf("统计表中学生数量:请选:5\n");
printf("查看表中学生信息:请选:6\n");
printf("排序学生平均成绩:请选:7\n");
printf("保存学生信息表到磁盘:请选:8\n");
printf("退出:请选:0\n");
do
{ printf("你的选择是:");
fflush(stdin);
scanf("%d",&func);
if(!((func >= 0)&&(func <= 8)))
printf("选择有误,请重新输入\n");
}
while(!((func >= 0)&&(func <= 8)));


return func;

}

struct student * app(struct student *a)
{
struct student *head;
struct student *checkflag;
struct student *p;
struct student *tail;
char name[20],flag = 'y';
// float average;
head = a;
checkflag = NULL;
p = NULL;
tail = NULL;
while(flag == 'y')
{
p = (struct student *)malloc(sizeof(struct student));
if(p == NULL)
{
printf("内存申请失败!\n");
exit(1);
}
else
{
do
{
printf("请输入学生学号:");
fflush(stdin);
scanf("%3d",&(p->stu_no));
if(head != NULL)
{
checkflag = check(p->stu_no,head);
if(checkflag != NULL)
printf("学生学号重复,请重新输入!\n");
}
}while(checkflag != NULL);
fflush(stdin);
// printf("学生学号:%d",p->stu_no);
printf("请输入学生姓名:");
gets(name);
// printf("\n学生姓名:%s",name);
strcpy(p->stu_name,name);
printf("请输入学生语文成绩:");
p->sco.chin = scorceinput();
printf("请输入学生数学成绩:");
p->sco.math = scorceinput();
printf("请输入学生英语成绩:");
p->sco.engl = scorceinput();
p->aver = avera(p->sco.chin,p->sco.math,p->sco.engl);
printf("\n");
printf("学号\t姓名\t语文\t\t数学\t\t英语\t\t平均成绩\n");
printf("%d\t%s\t%f\t%f\t%f\t%f\n",p->stu_no,p->stu_name,p->sco.chin,p->sco.math,p->sco.engl,p->aver);
if(head == NULL)
{
head = p;
tail = head;
// head->next = NULL;
tail->next = NULL;
}
else
{
if(head->next == NULL)
{
head->next = p;
tail = p;
tail->next = NULL;
}
else
{
tail = head->next;
while(tail->next != NULL)
{
tail = tail->next;
}
tail->next = p;
tail = p;
tail->next = NULL;
}
}
}
printf("是否打算继续追加?是/否(y/n)?: ");
fflush(stdin);
flag = getchar();
}
//if(head != NULL)
// {
// tail->next = NULL;
// }
return head;
}

struct student * print(struct student *head)
{
// struct student *head;
struct student *p;
// struct student *tail;
char t = 'n';
// head = l;
p = NULL;
// tail = NULL;
do
{
if(head == NULL)
{
printf("学生成绩管理信息系统数据为空\n");
printf("学生成绩管理信息系统数据: 创建(请按c),装载(请按l),放弃退出(请按a或其它键)?: ");
fflush(stdin);
t = getchar();
if(t == 'y')
head = create(head);
else if(t == 'l')
head = load(head);
else
{
printf("你已放弃了学生成绩管理信息系统的再建立或装载,程序即将退出!\n");
exit(1);
}
if(head == NULL)
{
printf("学生成绩管理信息系统的再建立或装载失败,程序即将退出!");
exit(1);
}
}
}while(head == NULL);
p = head;
printf("*********学生成绩管理信息系统表*********\n");
printf("*****************************************\n");
printf("学号\t姓名\t语文\t\t数学\t\t英语\t\t平均成绩\n");
do
{
printf("%d\t%s\t%f\t%f\t%f\t%f\n",p->stu_no,p->stu_name,p->sco.chin,p->sco.math,p->sco.engl,p->aver);
if(p->next == NULL)
{
p = NULL;
}
else
p = p->next;
}while(p != NULL);
printf("*********显示完成!*********\n");
return head;
}

void cout(struct student *head)
{
int i,num;
if(head == NULL)
{
num = 0;
}
else
{
num = 1;
for(i = 2;head->next != NULL;i++)
{
head = head->next;
num = i;
}

}
printf("学生总人数为:%d 个!\n",num);
}

weivs929 2008-06-24
  • 打赏
  • 举报
回复
本人刚学这个理解有限~还是请大虾写个完整的让我参考吧 不胜感激!!!
K行天下 2008-06-24
  • 打赏
  • 举报
回复
[Quote=引用楼主 weivs929 的帖子:]
刚开始学这个 ,有很多地方不懂 望高手赐教!
1.由键盘输入每个学生的学号。姓名和四门成绩
2.计算每个学生的平均分和总分
3.按部分排序(从高到底)并按名次输出
4.根据要求输出某门功课(由键盘输入课程号)成绩在90分以上(包括90分)的学生概况
具体要求
1,每个功能为一个独立的函数
2.所有的键盘输入要求提示
[/Quote]

void menu()
{
printf("1. 输入学生信息\n");
prinf("2 .
...
printf("0. 退出
...
}
int main()
{
int n;
int over = 0;
while(!over)
{
scanf("%d",n);

switch(n)
{

case 1: input();
break;
case 2:
...
case 0: over = 1; break;
weivs929 2008-06-24
  • 打赏
  • 举报
回复
谢啦 不过还是不太明白的 我的想法是选用一个主函数 然后把每个独立的函数作为子函数时行编写的。至于结构体那些我还会的 。主要是缺少整体的框架吧 。
oxiaoano 2008-06-24
  • 打赏
  • 举报
回复
不错。
macfan 2008-06-24
  • 打赏
  • 举报
回复
贴个,人数什么的要求修改下人数变量就行了.
什么项目要还是不要添加删除就行了.

#include<iostream>
#include<iomanip>
using namespace std;

const int consureN=5;//每人的信息量
const int LINE=3;//学生的人数
int score[LINE][consureN];
char zifu[][5]={"学号",
"班级",
"语文",
"数学",
"英语"};
int allscore[LINE];
int pingjun[3];


void main()
{
//Input();
// all();
//paiming();
// pingjunfen();
for(int i=0;i<LINE;i++)
{
for(int j=0;j<consureN;j++)
{
cout<<zifu[j]<<":"<<"\t";
cin>>score[i][j];//输入3个人每个人的信息,没人5条信息,全部为INT型,
//输入第一个学生的存入score[0][0],.....
}
cout<<endl;

}


for (int g=0;g<LINE;g++)
allscore[g] = 0;//全部初始化为0
for(int x=0;x<LINE;x++)
{
for(int j=2;j<consureN;j++)
{
allscore[x]=score[x][j]+allscore[x];//除了学号信息全部相加
}
}


for(int k=0;k<LINE;k++)
pingjun[k]=allscore[k]/(consureN-2);//得到三个人的平均分,赋值给PJ123


for(int s=0;s<LINE;s++)
{
for(int j=s+1;j<LINE;j++)
{
if(allscore[s]<allscore[j])
{
int t;
t=allscore[s];
allscore[s]=allscore[j];
allscore[j]=t;//交换数组,按从大到小排序
}
}
}

cout<<"排名"<<"\t"<<"学号"<<"\t\t"<<"班级"<<"\t"<<"语文"<<"\t"<<"数学"<<"\t"<<"英语"<<"\t"<<"平均分"<<"\t\t"<<"总分"<<endl;
cout<<"********************************************************************************"<<endl;

for(int w=0;w<LINE;w++)
{
int s=0;
cout<<w+1<<"\t"<<score[w][s]<<"\t\t"<<score[w][s+1]<<"\t"<<score[w][s+2]<<"\t"<<score[w][s+3]<<"\t"<<score[w][s+4]<<"\t"<<pingjun[w]<<"\t\t"<<allscore[w]<<endl;
}
}

zxg_1023 2008-06-23
  • 打赏
  • 举报
回复
//建议定义个结构体:
typedef score{
int number;
char name[MAX_NAME_LENTH];
float score1;
float score2;
float score3;
float score4;
}t_score;

//同时还有宏定义:
#define MAX_NAME_LENTH 50
#define MAX_STUDENT 100

//定义一个数组
t_score Student[MAX_STUDENT];
//初始化
memset(Student, 0, MAX_STUDENT*sizeof(t_score));

//输入信息,只是参考
scanf("%d", &(Student[i].number));
scanf("%s", &(Student[i].name));
scanf("%f", &(Student[i].score1));
scanf("%f", &(Student[i].score2));
scanf("%f", &(Student[i].score3));
scanf("%f", &(Student[i].score4));

//求某门成绩的平均分和总分就用循环对某门分数进行累加

//排序的方法一般C语言的书里都有介绍,按照某一门成绩排就行了

//第四个也简单,就循环判断某门成绩是否>=90就行了


呵呵,时间不多,只是大体上说下,洗澡——睡觉——
实验一 复习C++有关知识
实验目的:
通过实验掌握下列知识:
1、复习C++有关基本知识;
2、熟悉VC编程、编译和调试环境;
内容及步骤:
编写一个类Complex,定义复数的加法、减法、乘法和除法运算,要求在编写该类时重载这些运算操作符,并重载I/O操作符,以便输入和输出复数;
实验报告要求:
按要求写出完整的实验代码;

实验二 单链表结构及计算
实验目的:
通过实验掌握下列知识:
1、熟悉线性表的基本运算在两种存储结构(顺序结构和链式结构)上的实现;
2、继续熟悉VC编程、编译和调试环境;
内容及步骤:
1、 设有一个线性表(e0,e1,e2,e3,…,en-2,en-1)存放在一个一维数组A[arraySize]中的前n个数组元素位置。请编写一个函数将这个线性表原地逆置,即将数组的前n个原地址内容置换为(en-1,en-2,…,e3,e2,e1,e0)。
2、 针对带附加头结点的单链表,试编写下列函数:
A. 定位函数Locate:在单链表中寻找第i个结点。若找到,则函数返回第i个结点的地址;若找不到,则函数返回NULL;
B. 球最大值函数max:通过单链表的一趟遍历,在单链表中确定值最大的结点;
C. 统计函数number:统计单链表中具有给定值x的所有元素数量;
D. *建立函数create:根据一维数组a[n]建立一个单链表,使单链表中各元素的次序与a[n]中各元素的次序相同,要求该程序的时间复杂度为O(n)。
E. *整理函数tideup:在非递减有序的单链表中删除值相同的多余结点。
实验报告要求:
按要求写出完整的实验代码;

实验三 堆栈结构与递归
实验目的:
通过实验掌握下列知识:
1、掌握堆栈的结构和运算应用;
2、掌握并运用递归的概念进行编程;
内容及步骤:
1、 借助堆栈实现单链表上的逆置运算;
要求: a. 用C++编程;
b. 首先用C++实现单链表编程,再基于编写好的单链表类,实现堆栈类的定义和实现。
c. 链表类和堆栈类都要包含必要的成员函数(按照教材要求)。
2、 已知a[n]为整数数组,试写出实现下列运算的递归代码(C或C++代码均可):
要求: a. 求数组中的最大整数;
b. 求n个数的和;
c. 利用堆栈类,将本题a和b的代码改成非递归的方式。
实验报告要求:
按要求写出完整的实验代码;

实验四 综合(课程设计)
内容及步骤:
1、假定一维数组a[n]中的每个元素值均在[0,200]区间内,用C++编写一个算法,分别统计出落在[0,20],[21,50],[51,80],[81,130],[131,200]等各区间内的元素个数。
2、 请用C++编写一个算法,完成以下功能:
a. 从键盘输入一段文字,以$作结束符号;
b. 统计文字中的文本行数,字母,数字以及其他符号的数量,并在屏幕上显示;
3、 请用C++编写一个算法,完成矢量的加法与成法运算,运算规则如下:
a. 矢量加法:(a1,a2,……,an)+(b1,b2,……,bn)=(a1+b1,a2+b2,……,an+bn);
b. 矢量减法:(a1,a2,……,an)-(b1,b2,……,bn)=(a1-b1,a2-b2,……,an-bn);
c. 矢量点积:(a1,a2,……,an)*(b1,b2,……,bn)=(a1*b1,a2*b2,……,an*bn);
d. 矢量与实数相乘:a*(b1,b2,……,bn)=(a*b1,a*b2,……,a*bn);
4、 请用C++结合链表编写一个简单的机票订票程序,要求完成以下功能:
a. 允许出现多个班机;
b. 创建一个机链表,每个节点都包含指向一个乘客链表的指针;
c. 该程序要有顾客购票,查询班机起飞降落时间,班机订票情况等3个功能,并实现菜单选项
5、 用C++编写一个简单的行编辑器,每个结点保存一行文本,程序以E file开始,然后显示行数和提示符,如果输入I,后面跟着一个数字n,就在第n行之前插入后续文本,如果I后面没有跟数字,就在当前行之前插入文本,如果输入D,后面跟着m,n,一个数字n或者没有数字,就分别删除m到n行,第n行或者当前行,命令L用于显示文本;
6、 用C++编写求多项式的和与积的算法,要求如下:
a. 要求从键盘分别输入2个多项式的系数以及最高次幂;
b. 通过重载操作符+和*,完成多项式的和与积的计算;
c. 输出运算结果;
7、 编写一个程序,将10进制数转换为其它(2-9)进制数。可以将要转换的数重复除以基数,然后讲除的余数按反方向排列来实现;
8、 已知A[n]为正数数组,试写出实现下列运算的递归算法;
a. 求数组A中的最大整数;
b. 求n个数的平均值;
c. 求n个整数的平均值;
9、 已知f为单链表的表头指针,链表中存储的都是整型数据,试写出实现下列运算的递归算法:
a. 求链表中的最大整数;
b. 求链表的结点个数;
c. 求所有整数的平均数;
告要求:
写出能运行的完整的代码。

实验五 二叉树(一)
实验目的:
通过实验掌握下列知识:
1、熟悉二叉树的存储结构和遍历算法;
2、通过二叉树遍历操作了解递归的本质和方法;
内容及步骤:
1、 试建立一个二叉搜索树,并实现以下成员函数:
a. 默认构造函数和带数据域、左子树指针、右子树指针的构造函数;
b. 按照二叉搜索树的要求设计插入函数Insert(int Info);
c. 用递归的方法设计前序遍历和后续遍历函数,遍历时要输出遍历的每个结点;
d. 设计一个构造函数,当对象结束时,要释放整个二叉搜索树所占的内存空间(提示,通过后序遍历算法找到叶结点,并删除叶结点,不断重复此过程,直到整科树为空);
2、实现1所要求的代码后,运行设计好的代码,将以下的几组整数序列建成搜索二叉树,并记录下它们的前序遍历序列和后序遍历序列:
a. 1、3、5、7、9;
b. 1、13、35、13、27;
c. 50、25、78、13、44、99、66。
实验报告要求:
1、 按要求记录下二叉搜索树的完整实验代码;
2、 按要求记录下要求的输出结果。


实验六 二叉树(二)
实验目的:
通过实验掌握下列知识:
1、继续熟悉二叉树的存储结构和遍历算法;
2、熟悉二叉搜索树的应用,并做一个小型的课程设计;
内容及步骤:
1、 在前一个实验的基础上,继续增加搜索函数Search(int Info)(如果找到结点,返回指向该结点的指针,如果没有,则返回空指针)和删除函数bool Delete(int Info),如果找到结点,则删除该结点,并保持二叉搜索树的基本结构,并返回true,否则返回false;
2、利用二叉搜索树实现一个音像商店(小型书店、小型超市、或小型药店)的交易管理系统,要求实现以下功能:
a. 该系统应该有一个字符型的主菜单;
b. 能按字母顺序显示库存商品的名称和数量;
c. 能添加和删除新的商品;
d. 当输入一个商品时,能显示该商品是否在库存中,如存在库存中,则显示其名称和数量,否则显示“未找到”。
e. 如有可能,请建立一个存储商品名称和数量的文本文件,并为二叉搜索树建立一个成员函数SetupInventory(),用于从该文本文件中读取库存商品的数据,
实验报告要求:
1、 按要求记录下二叉搜索树的完整实验代码;
2、 按要求记录下要求的输出结果。

实验六 图(课程设计)
实验目的:
通过实验掌握下列知识:
1、熟悉图的存储结构和遍历算法;
2、熟悉图的应用,并做一个小型的课程设计;
内容及步骤:
1、 设计一个图的类,采用临接表法进行存储,该图每个结点的数据类型类模板的模板参数进行定义(注:需先设计一个结点类Node);
2、 为该类分别设计一个实现深度优先搜索和广度优先搜索的成员函数,并要输出搜索结果;
注: 1、为了让你设计的图类拥有数据,可以设计一个成员函数,用于构造你自己预先设计好的图;
2、要求的图如下,也可以自己构造图,但是需要注意的是,图不能是退化的单链表:

实验报告要求:
1、 按要求记录下图的类的完整实验代码;
2、 纪录你所使用的图;
3、 按要求记录下要求的输出结果;


实验八 综合实验
内容及步骤:
1、请使用C++编写班级学生学籍管理程序
每个学生的信息包括:姓名、学号和英语、数学、程序设计及体育成绩。从键盘输入数据,建立数据文件student.dat,然后,利用C++编程完成如下处理:
(1)对学生姓名或学号进行查询,显示其信息 。
(2)对所有学生,按班级计算每一科平均成绩。
(3)分别按英语、数学、程序设计及体育成绩排序并输出到文件。
注:要用面向对象的方法来设计程序,每个班是一个类的实例;
2、用链表建立通讯录。通讯录内容有:姓名、通讯地址、电话号码。
(1)通讯录是按姓名项的字母顺序排列的;
(2)能查找通讯录中某人的信息;
(3)能添加和删除通讯录中的指定项。
注:要用面向对象的方法来设计程序,每个通讯录是一个类的实例;
3、从终端读入字符集大小为n(即字符的个数),逐一输入n个字符和相应的n个权值(即字符出现的频度),建立哈夫曼树,进行编码并且输出。
注:可用C或C++编写
4、用邻接矩阵或邻接图实现一个有向图的存储,并实现单源最短路径算法的实现(这个类的一个成员函数),并能输出该图的关键路径。
注:1、要用面向对象的方法设计代码;
2、一个图是一个类的实例;
3、类中要指出该图的起点。
实验报告要求:
写出完整的代码。



习题1 绪论------------------------------------------------------------------------------------6
习题2 线性表---------------------------------------------------------------------------------8
习题3 栈和队列------------------------------------------------------------------------------11
习题4 串---------------------------------------------------------------------------------------13
习题5 数组------------------------------------------------------------------------------------15
习题6 树与二叉树---------------------------------------------------------------------------17
习题7 图---------------------------------------------------------------------------------------24
习题8 查找------------------------------------------------------------------------------------31
习题9 排序------------------------------------------------------------------------------------34

第1部分 C++基本知识
各种数据结构以及相应算法的描述总是要选用一种语言工具。在计算机科学发展过程中,早期数据结构教材大都采用PASCAL语言为描述工具,后来出现了采用C语言为描述工具的教材版本、至今又出现了采用C++语言为描述工具的多种教材版本。本教实验指导书是为已经学习过C++语言的学生而编写编写实验指导书目的为了配合理论教学。程序要求在C++ Builder开发环境之下调试运行,采用面向对象方法进行设计。典型的数据结构被设计成为类(class),典型算法设计成为类的函数成员,然后在主函数中声明创建类对象,根据实际需要调用重要的算法。
由于C++的使用具有一定的难度,为了同学更好的学习数据结构自身的知识内容,减轻描述工具所带来的困难,这里针对数据结构上机实验所必须的C++基本知识(结构体、类等等)做补充介绍。
一、 源程序组成

这部分内容详细参见本指导书的第3部分的程序实例。
二、结构体及运用
数据结构课程所研究的问题均运用到“结构体”和“类”。在C++语言中结构体和函数又是理解和掌握“类”的语法基础。定义结构体的一般格式:
struct 结构体类型名
{ 类型名1 变量名1; //数据子域
类型名2 变量名2;……
类型名n 变量名n;
}
其中struct是保留字。结构体类型名由用户自己命名。在使用时必须声明一个具体的结构体类型的变量,声明创建一个结构体变量的方法是:
结构体类型名 结构体变量名;
一个结构体中可以包含多个数据子域。数据子域的类型名一般指基本数据类型(int char 等),也可是已经定义的另一结构体名。数据子域变量名可以是简单变量,也可以是数组。它们也可以称为结构体的数据成员,它们的访问控制具有‘公有’属性。
1. 通过“结构体变量名.数据子域” 可以访问数据子域。
// 设计Student结构体,在主程序中运用。
#include
#include
#include
struct Student //定义结构体Student
{ long num; // 学号
int x; // 成绩
char name[10]; // 姓名
}
int main( )
{ Student s1; //声明创建一个结构体变量s1
//为s1的数据子域提供数据
s1.num=1001 ;
s1. x=83;
strcpy( s1.name, “ 李 明”);
//输出结构体变量s1 的内容
cout<< “ 姓名: ”<< s1.name <cout<< “ 学号: ”<< s1.num<cout<< “ 成绩:”<< s1.x <_getch(); return 0;
}
2. 设计一维数组,每个数组元素是Student结构体类型,通过以下语句段可以说明结构体数组的一般用法:通过“结构体数组名[下标].数据子域”访问数据域。
Student a[5]; //声明创建一个结构体数组a
for(int i=0, i<5, i++)
{ cout<<“学号:”; cin>>a[i].num; //输出数组元素a[i]的学号域
cout<<“姓名:”; cin>> a[i].name; //输出数组元素a[i]的姓名域
cout<<“成绩:”; cin>>a[i].x; //输出数组元素a[i]的成绩域
}
以上是关于结构体的基本概念和简单运用。


三、 类的基本概念及运用
类的是面向对象程序的基本单位。类是由数据成员和相关的函数成员组成。从面向对象的角度考虑“学生”这个类,它不仅包括“学生”的一般属性:学号、姓名、成绩等等,还应包括对于这些属性的操作:输入/输出、听课、实验、等等。
类定义的一般格式:
class 类名
{ 若干数据成员;
若干函数成员;
};
类的数据成员和函数成员均存在访问控制权限问题。访问控制分为三种:公有(public)、私有(private)和受护(protected)。
数据成员的定义和结构体中的数据域定义是相似的。不同的是它们必须明确访问控制。而公有数据成员,可以认为与结构体的数据域的访问权限相同。
成员函数的定义又和一般函数的定义基本相同。不同的是类中成员函数也必须明确访问控制权限。如果在类之中定义成员函数带函数体,并未有什么特殊之处。如果在类之中仅有成员函数的原型声明,当在类定义之外定义函数体时,需要加上类限定标识“类名::”。下面是“学生”类的定义:
class Students //定义类结构体Students
{ private: //私有成员
long num; // 学号
int x; // 成绩
char name[10]; // 姓名
public: //公有成员
Students();
~Students() { };
void SetDat( long n, int x0, char *na0 )
{ num=n; x=x0; strcpy( name,na0);
}
void PrintOut( ); //输出函数的原型声明
…….;
};
void Students::PrintOut( ) // 输出函数前加Students::
{ cout<< “ 姓名: ”<< name <cout<< “ 学号: ”<< num<cout<< “ 成绩:”<< x < }
在主程序中运用类 Students。
int main( )
{ Students s; //声明创建一个类对象s,调用构造函数
s.PrintOut( ); //输出s的内容
long m; int y; char xname[10];
cout<< “ 输入学号,成绩,姓名:” ;
cin>>m>>y>>xname;
s. SetDat( m, y, xname ) ; //修改对象s数据
s. PrintOut(); //输出改变后s的内容
_getch(); return 0;
}
运行结果:
姓名:O
学号:0
成绩:0
输入学号,成绩,姓名:1001 90 WangMing
姓名:WangMing
学号:1001
成绩:90
这个例题中数据成员全部定义为私有(private),以便保证数据安全性。
而函数成员全部定义为公有(public)成员函数,可以作为类对外部的的接口。 通过s. SetDat( m, y, xname ) ; 直接访公有函数成员SetDat( ), 将实参(主函数的局部变量m, y, xname) 的数据赋给私有数据成员 num,x,name。 通过 s.PrintOut( );直接访公有函数成员PrintOut( ),间接访问输出私有成员num,x,name。
四、 结构体在类中的使用
1.结构体数组做类的数据成员
const int MAXSIZE=100; // 数组的容量
struct ElemType // 数据元素的类型
{ int numb;
char name[20];
long tel;
};
class Sqlist
{ private:
ElemType elem[MAXSIZE]; //结构体ElemType类型的数组elem[ ]做数据成员
int length;
public:
Sqlist( void);
~Sqlist(){ };
//其他函数……
};
2.结构体指针变量做类的数据成员
struct NodeType // 结点的结构定义
{ int data; // 数据域
NodeType *next; // 指针域
};
class Link //类声明
{ private:
NodeType *Head; //指向结构构体NodeType的指针变量Head做数据成员
public:
Link ( ){ Head=new NodeType; // 为头结点申请空间
Head->next=Head; // 头结点Head 形成空环
};
~ Link (){ };
void creat();
void outs();
};



















第2部分 书面练习题
习题1 绪论
1.1 单项选择题
1. 数据结构是一门研究非数值计算的程序设计问题中,数据元素的① 、数据信息在计算机中的② 以及一组相关的运算等的课程。
① A.操作对象   B.计算方法  C.逻辑结构  D.数据映象
② A.存储结构 B.关系 C.运算 D.算法
2. 数据结构DS(Data Struct)可以被形式地定义为DS=(D,R),其中D是① 的有限集合,R是D上的② 有限集合。
① A.算法 B.数据元素 C.数据操作 D.数据对象
② A.操作 B.映象 C.存储 D.关系
3. 在数据结构中,从逻辑上可以把数据结构分成 。
A.动态结构和静态结构 B.紧凑结构和非紧凑结构
C.线性结构和非线性结构 D.内部结构和外部结构
4. 算法分析的目的是① ,算法分析的两个主要方面是② 。
① A. 找出数据结构的合理性 B. 研究算法中的输入和输出的关系
C. 分析算法的效率以求改进 D. 分析算法的易懂性和文档性
② A. 空间复杂性和时间复杂性 B. 正确性和简明性
C. 可读性和文档性 D. 数据复杂性和程序复杂性
5. 计算机算法指的是① ,它必具备输入、输出和② 等五个特性。
① A. 计算方法 B. 排序方法
C. 解决问题的有限运算序列 D. 调度方法
② A. 可行性、可移植性和可扩充性 B. 可行性、确定性和有穷性
C. 确定性、有穷性和稳定性 D. 易读性、稳定性和安全性

1.2 填空题(将正确的答案填在相应的空中)
1. 数据逻辑结构包括 、 和 三种类型,树形结构和图形结构合称为 。
2. 在线性结构中,第一个结点 前驱结点,其余每个结点有且只有 个前驱结点;最后一个结点 后续结点,其余每个结点有且只有 个后续结点。
3. 在树形结构中,树根结点没有 结点,其余每个结点有且只有 个直接前驱结点,叶子结点没有 结点,其余每个结点的直接后续结点可以 。
4. 在图形结构中,每个结点的前驱结点数和后续结点数可以 。
5. 线性结构中元素之间存在 关系,树形结构中元素之间存在 关系,图形结构中元素之间存在 关系。
6. 算法的五个重要特性是__ __ , __ __ , ___ _ , __ __ , _ ___。
7. 分析下面算法(程序段),给出最大语句频度 ,该算法的时间复杂度是__ __。
for (i=0;i for (j=0;j A[i][j]=0;
8. 分析下面算法(程序段),给出最大语句频度 ,该算法的时间复杂度是__ __。
for (i=0;i for (j=0; jA[i][j]=0;
9. 分析下面算法(程序段),给出最大语句频度 ,该算法的时间复杂度是__ __。
s=0;
for (i=0;i for (j=0;j for (k=0;k s=s+B[i][j][k];
sum=s;
10. 分析下面算法(程序段)给出最大语句频度 ,该算法的时间复杂度是__ __。
i=s=0;
while (s{ i++;
s+=i; //s=s+i
}
11. 分析下面算法(程序段)给出最大语句频度 ,该算法的时间复杂度是__ __。
i=1;
while (i<=n)
i=i*2;
1.3 算法设计题
1. 试写一算法,自大到小依次输出顺序读入的三个数X,Y和Z的值.
2. 试写一算法,求出n个数据中的最大值。写出最大语句频度,该算法的时间复杂度。
习题答案
1.1 1. C , A 2. B,D 3. C 4. C, A 5. C,B
1.2 1. 线性结构、树形结构、图形结构,非线性结构
2. 没有、1、没有、1
3. 前驱、1、后续、任意多个
4. 任意多个
5. 一对一、一对多、多对多
6. 有穷性、确定性、可行性、输入、输出
7. 最大语句频度:n2 , 时间复杂度:. O (n2)
8. 最大语句频度:n (n+1)/2 , 时间复杂度:. O (n2)
9. 最大语句频度:n3 , 时间复杂度:. O (n3)
10. 最大语句频度:n , 时间复杂度:. O (n )
11. 最大语句频度:log2n, 时间复杂度:. O (log2n )




习题2 线性表
2.1 单项选择题
1. 一个向量(即一批地址连续的存储单元)第一个元素的存储地址是100,每个元素的长度为2,则第5个元素的地址是__ __。
A. 110 B. 108 C. 100 D. 120
2. 线性表的顺序存储结构是一种__ _的存储结构,而链式存储结构是一种__ _的存储结构。
A.随机存取 B.索引存取 C.顺序存取 D.散列存取
3. 线性表的逻辑顺序与存储顺序总是一致的,这种说法__ _。
A. 正确 B. 不正确
4. 线性表若采用链式存储结构时,要求内存中可用存储单元的地址__ _。
A. 必须是连续的 B. 部分地址必须是连续的
C. 一定是不连续的 D. 连续或不连续都可以
5. 在以下的叙述中,正确的是__ _。
A. 线性表的顺序存储结构优于链表存储结构
B. 线性表的顺序存储结构适用于频繁插入/删除数据元素的情况
C. 线性表的链表存储结构适用于频繁插入/删除数据元素的情况
D. 线性表的链表存储结构优于顺序存储结构
6. 每种数据结构都具备三个基本运算:插入、删除和查找,这种说法__ _。
A. 正确 B. 不正确
7. 不带头结点的单链表head为空的判定条件是____。
A. head= =NULL B. head->next= =NULL
C. head->next= =head D. head!=NULL
8. 带头结点的单链表head为空的判定条件是____。
A. head= =NULL B. head->next= =NULL
C. head->next= =head D. head!=NULL
9. 非空的循环单链表head的尾结点(由p所指向)满足____。
A. p->next= =NULL B. p= =NULL
C. p->next= =head D. p= =head
10. 在双向循环链表的p所指结点之后插入s所指结点的操作是____。
A. p->right=s; s->left=p; p->right->left=s; s->right=p->right;
B. p->right=s; p->right->left=s; s->left=p; s->right=p->right;
C. s->left=p; s->right=p->right; p->right=s; p->right->left=s;
D. s->left=p; s->right=p->right; p->right->left=s; p->right=s;
11. 在一个单链表中,已知q所指结点是p所指结点的前驱结点,若在q和p之间插入s结点,则执行____。
A. s->next=p->next; p->next=s; B. p->next=s->next; s->next=p;
B. q->next=s; s->next=p; C. p->next=s; s->next=q;
12. 在一个单链表中,若p所指结点不是最后结点,在p之后插入s所指结点,则执行____。
A. s->next=p; p->next=s; B. s->next=p->next; p->next=s;
C. s->next=p->next; p=s; C. p->next=s; s->next=p;
13. 在一个单链表中,若删除p所指结点的后续结点,则执行____。
A. p->next= p->next->next; B. p= p->next; p->next= p->next->next;
C. p->next= p->next; D. p= p->next->next;
14. 从一个具有n个结点的单链表中查找其值等于x结点时,在查找成功的情况下,需平均比较____个结点。
A. n B. n/2 C. (n-1)/2 D. (n+1)/2
15. 在一个具有n个结点的有序单链表中插入一个新结点并仍然有序的时间复杂度是__ __。
A. O(1) B. O(n) C. O (n2) D. O (nlog2n)
16. 给定有n个元素的向量,建立一个有序单链表的时间复杂度是__ __。
A. O(1)) B. O(n) C. O (n2) D. O (n*log2n)
2.2 填空题(将正确的答案填在相应的空中)
1. 单链表可以做__ __的链接存储表示。
2. 在双链表中,每个结点有两个指针域,一个指向____ __,另一个指向___ __。
3. 在一个单链表中p所指结点之前插入一个s (值为e)所指结点时,可执行如下操作:
q=head;
while (q->next!=p) q=q->next;
s= new Node; s->data=e;
q->next= ; //填空
s->next= ; //填空
4. 在一个单链表中删除p所指结点的后继结点时,应执行以下操作:
q= p->next;
p->next= _ ___; //填空
delete ; //填空
5. 在一个单链表中p所指结点之后插入一个s所指结点时,应执行s->next=__ __和p->next=____的操作。
6. 对于一个具有n个结点的单链表,在已知p所指结点后插入一个新结点的时间复杂度是__ __;在给定值为x的结点后插入一个新结点的时间复杂度是__ __。
2.3 算法设计题:
1.设顺序表va中的数据元数递增有序。试写一算法,将x插入到顺序表的适当位置上,以保持该表的有序性。
2.试写一算法,实现顺序表的就地逆置,即利用原表的存储空间将线性表(a1, a2,…. an)逆置为(an, an-1,…., a1)。
3. 已知线性表中的元素以值递增有序排列,并以单链表作存储结构。试写一算法,删除表中所有大于x且小于y的元素(若表中存在这样的元素)同时释放被删除结点空间。
4. 试写一算法,实现单链表的就地逆置(要求在原链表上进行)。
习题答案
2.1 1. B 2. A, C 3. B 4. D 5. C 6. A 7. A 8. B
9. C 10. D 11.B 12.B 13.A 14.D 15.B 16.C
2.2 1. 线性结表 2. 前驱结点、后继结点
3. s, p 4. q->next, q
5. p->next, s 6. O (1) , O (n)

习题3 栈和队列
3.1 单项选择题
1. 一个栈的入栈序列a,b,c,d,e,则栈的不可能的输出序列是____。
A. edcba B. decba C. dceab D. abcde
2. 若已知一个栈的入栈序列是1,2,3,…,n,其输出序列为p1,p2,p3,…,pn,若p1=n,则pi为____。
A. i B. n=i C. n-i+1 D. 不确定
3. 栈结构通常采用的两种存储结构是____。
A. 顺序存储结构和链式存储结构
B. 散列方式和索引方式
C. 链表存储结构和数组
D. 线性存储结构和非线性存储结构
4. 判定一个顺序栈ST(最多元素为m0)为空的条件是____。
A. top !=0 B. top= =0 C. top !=m0 D. top= =m0-1
5. 判定一个顺序栈ST(最多元素为m0)为栈满的条件是____。
A. top!=0 B. top= =0 C. top!=m0 D. top= =m0-1
6. 栈的特点是____,队列的特点是____。
A. 先进先出 B. 先进后出
7. 向一个栈顶指针为HS的链栈中插入一个s所指结点时,则执行__ __。
(不带空的头结点)
A. HS—>next=s;
B. s—>next= HS—>next; HS—>next=s;
C. s—>next= HS; HS=s;
D. s—>next= HS; HS= HS—>next;
8. 从一个栈顶指针为HS的链栈中删除一个结点时,用x保存被删结点的值,则执行__ __。(不带空的头结点)
A. x=HS; HS= HS—>next; B. x=HS—>data;
C. HS= HS—>next; x=HS—>data; D. x=HS—>data; HS= HS—>next;
9. 一个队列的数据入列序列是1,2,3,4,则队列的出队时输出序列是____ 。
A. 4,3,2,1 B. 1,2,3,4
C. 1,4,3,2 D. 3,2,4,1
10. 判定一个循环队列QU(最多元素为m0)为空的条件是____。
A. rear - front= =m0 B. rear-front-1= =m0
C. front= = rear D. front= = rear+1
11. 判定一个循环队列QU(最多元素为m0, m0= =Maxsize-1)为满队列的条件是____。
A. ((rear- front)+ Maxsize)% Maxsize = =m0
B. rear-front-1= =m0 C. front= =rear
D. front= = rear+1
12. 循环队列用数组A[0,m-1]存放其元素值,已知其头尾指针分别是front和rear,则当前队列中的元素个数是____。
A. (rear-front+m)%m B. rear-front+1
C. rear-front-1 D. rear-front
13. 栈和队列的共同点是____。
A. 都是先进后出 B. 都是先进先出
C. 只允许在端点处插入和删除元素 D. 没有共同点
3.2 填空题(将正确的答案填在相应的空中)
1. 向量、栈和队列都是____结构,可以在向量的____位置插入和删除元素;对于栈只能在____插入和删除元素;对于队列只能在____插入元素和____删除元素。
2. 向一个长度为n的向量的第i个元素(1≤i≤n+1)之前插入一个元素时,需向后移动____个元素。
3. 向一个长度为n的向量中删除第i个元素(1≤i≤n)时,需向前移动____个元素。
4. 向栈中压入元素的操作是____。
5. 对栈进行退栈时的操作是____。
6. 在一个循环队列中,队首指针指向队首元素的____。
7. 从循环队列中删除一个元素时,其操作是____。
8. 在具有n个单元的循环队列中,队满时共有____个元素。
9. 一个栈的输入序列是12345,则栈的输出序列43512是____。
10. 一个栈的输入序列是12345,则栈的输出序列12345是____。
3.3 算法设计题:
1. 输入一个任意的非负十进制整数,输出与其等值的八进值数。
2. 按照四则运算加、减、乘、除和幂运算(↑)优先关系的惯例,并仿照教科书3.2节例3—1的格式,画出对下列算术表达式求值时操作数栈和运算符栈的变化过程:
A-B*C/D+E↑F
3. 假设以带头结点的循环链表表示队列,并且只设一个指针指向队尾元素结点(注意不设头指针),试编写相应的队列初始化、入队列和出队列的算法。

习题答案
3.1 1. C 2. C 3. A 4. B 5.D 6. BA 7.C 8. B 9. C 10. C
11. A 12. A 13.C
3.2 1. 线性、任何、栈顶、队尾、队首 2. n-i+1 3. n-i
4.先移动栈顶指针,后存入元素 5. 先取出元素,后移动栈顶指针
6.前一个位置 7. 先移动队首元素,后取出元素
8. n-1 9. 不可能的 10. 可能的


习题4 串
4.1 单项选择题
1.以下叙述中正确的是 。
A.串是一种特殊的线性表 B.串的长度必须大于零
C.串中无素只能是字母 D.空串就是空白串
2.空串与空格串是相同的,这种说法____。
A. 正确 B. 不正确
3.串是一中特殊的线性表,其特殊性体现在____。
A. 可以顺序存储 B. 数据元素是一个字符
C. 可以链接存储 D. 数据元素可以是多个字符
4.设有两个串p和q,求q在p中首次出现的位置的运算称作____。
A. 连接 B. 模式匹配
C. 求子串 D. 求串长
5.设串s1=’ABCDEFG’,s2=’PQRST’,函数con (x,y)返回x和y串的连接串,subs(s,i,j)返回串s的从序号i的字符开始的j个字符组成的子串,len(s)返回串s的长度,则con (subs (s1,2,len (s2)), subs (s1,len (s2),2))的结果串是____。
A. BCDEF B. BCDEFG
C. BCPQRST D. BCDEFEF
6.设串的长度为n,则它的子串个数为 。
A.n B.n(n+1) C.n(n+1)/2 D.n(n+1)/2+1
4.2 填空题(将正确的答案填在相应的空中)
1.串的两种最基本的存储方式是____。
2.两个串相等的充分必要条件是____。
3.空串是____,其长度等于____。
4.空格串是____,其长度等于____。
5.设s=’I︺AM︺A︺TEACHER’,其长度是____。
4.3 判断题
1.串是由有限个字符构成的连续序列,串长度为串中字符的个数,子串是主串中
符构成的有限序列。 ()
2.子串定位函数的时间复杂度在最坏情况下为O(n*m),因此子串定位函数没有实际使用的价值。 ()
3.KMP算法的最大特点是指主串的指针不需要回溯。 ()
4.设模式串的长度为m,目标串的长度为n;当n≈m且处理只匹配一次的模式时,朴素的匹配(即子串定位函数)算法所花的时间代价也可能会更为节省。 ()
5.如果一个串中的所有字符均在另一串中出现,则说前者是后者的子串。 ()
4.3 算法设计题
1.编写算法,从串s 中删除所有和串 t相同的子串。
2.编写算法,实现串的基本操作Replace(&S,T,V)。
3.写一个递归算法来实现字符串逆序存储,要求不另设存储空间。
习题答案
4.1 1.A 2.B 3.B 4.B 5.D 6.C
4.2
1.顺序存储方式和链接存储方式
2.两个串的长度相等且对应位置的字符相同
3.零个字符的串、零
4.由一个或多个空格字符组成的串、其包含的空格个数
5.14
4.3 × × √ √ ×
4.4
3. void reverse(char arr[])
{char ch;
int i=1;
do{cin>>ch;
reverse(arr);
arr[i]=ch;
i++;
}while(ch!=’#’&&i}

习题5 数组和广义表
5.1 单项选择题
1. 常对数组进行的两种基本操作是____。
A. 建立与删除 B. 索引和修改
C. 对数据元素的存取和修改 D. 查找与索引
2. 二维数组M的成员是6个字符(每个字符占一个存储单元,即一个字节)组成的串,行下标i的范围从0到8,列下标j的范围从0到9,则存放M 至少需要①_ _个字节;M数组的第8列和第5行共占②____个字节。
① A. 90 B. 180 C. 240 D. 540
② A. 108 B. 114 C. 54 D. 60
3. 二维数组A中,每个元素的长度为3个字节,行下标i从0到7,列下标j从0到9,从首地址SA开始连续存放在存储器内,存放该数组至少需要的字节数是____。
A. 80 B. 100 C.240 D. 270
4. 二维数组A中,每个元素A的长度为3个字节,行下标i从0到7,列下标j从0到9,从首地址SA开始连续存放在存储器内,该数组按行存放时,数组元素A[7][4]的起始地址为____。
A. SA+141 B. SA+144 C. SA+222 D. SA+225
5. 二维数组A中,每个元素A的长度为3个字节,行下标i从0到7,列下标j从0到9,从首地址SA开始连续存放在存储器内,该数组按列存放时,元素A[4][7]的起始地址为____。
A. SA+141 B. SA+180 C. SA+222 D. SA+225
5.2 填空题(将正确的答案填在相应的空中)
1. 已知二维数组A[m][n]采用行序为主方式存储,每个元素占k个存储单元,并且第一个元素的存储地址是LOC(A[0][0]),则A[i][j]的地址是_______。
2. 二维数组A[10][20]采用列序为主方式存储,每个元素占一个存储单元并且A[0][0]的存储地址是200,则A[6][12]的地址是____。
3. 二维数组A[10..20][5..10]采用行序为主方式存储,每个元素占4个存储单元,并且A[10][5]的存储地址是1000,则A[18][9]的地址是____。
4.求下列广义表操作的结果:
(1) GetTail[GetHead[((a,b),(c,d))]];
(2) GetTail[GetHead[GetTail[((a,b),(c,d))]]]
5.利用广义表的GetHead和GetTail操作写出如上题的函数表达式,把原子banana分别从下列广义表中分离出来.
(1) L1=(((apple)),((pear)),(banana),orange);
(2) L2=(apple,(pear,(banana),orange));
5.3 算法设计题:
1. 假设稀疏矩阵A和B均以三元组顺序表作为存储结构。试写出矩阵相加的算法,另设三元组表C存放结果矩阵。
2. 假设系数矩阵A和B均以三元组顺序表作为存储结构。试写出满足以下条件的矩阵相加的算法:假设三元组顺序表A的空间足够大,将矩阵B加到矩阵A上,不增加A,B之外的附加空间,你的算法能否达到O(m+n)的时间复杂度?其中m和n分别为A,B矩阵中非零元的数目。
3.试编写一个以三元组形式输出用十字链表表示的稀疏矩阵中非零元素及其下标的算法。
习题答案
5.1 1. C 2. D,A 3.C 4. C 5. B
5.2 1. LOC (A[0][0])+(n*i+j)*k 2. 200+(6*20+12)= 326
3. 1000+((18-10)*6 +(9-5))*4 = 1208
4.(1). (b) (2). (d)
5. (1) GetHead [GetHead[GetTail[GetTail[L1]]]];
(2) GetHead [GetHead [GetHead[GetTail[L2 ]]]];

习题6 树和二叉树
6.1 单项选择题
1. 由于二叉树中每个结点的度最大为2,所以二叉树是一种特殊的树,这种说法____。
A. 正确 B. 错误
2. 假定在一棵二叉树中,双分支结点数为15,单分支结点数为30个,则叶子结点数为 个。 A.15 B.16 C.17 D.47
3. 按照二叉树的定义,具有3个结点的不同形状的二叉树有____种。
A. 3 B. 4 C. 5 D. 6
4. 按照二叉树的定义,具有3个不同数据结点的不同的二叉树有____种。
A. 5 B. 6 C. 30 D. 32
5. 深度为5的二叉树至多有____个结点。
A. 16 B. 32 C. 31 D. 10
6. 设高度为h的二叉树上只有度为0和度为2的结点,则此类二叉树中所包含的结点数至少为_ ___。
A. 2h B. 2h-1 C. 2h+1 D. h+1
7. 对一个满二叉树,m个树叶,n个结点,深度为h,则____ 。
A. n=h+m B. h+m=2n C. m=h-1 D. n=2 h-1
8. 任何一棵二叉树的叶结点在先序、中序和后序遍历序列中的相对次序____。
A.不发生改变 B.发生改变 C.不能确定 D.以上都不对
9. 如果某二叉树的前根次序遍历结果为stuwv,中序遍历为uwtvs,那么该二叉树的后序为____。 A. uwvts B. vwuts C. wuvts D. wutsv
10. 二叉树的前序遍历序列中,任意一个结点均处在其子女结点的前面,这种说法____。 A. 正确 B. 错误
11. 某二叉树的前序遍历结点访问顺序是abdgcefh,中序遍历的结点访问顺序是dgbaechf,则其后序遍历的结点访问顺序是____。
A. bdgcefha B. gdbecfha C. bdgaechf D. gdbehfca
12. 在一非空二叉树的中序遍历序列中,根结点的右边____。
A. 只有右子树上的所有结点 B. 只有右子树上的部分结点
C. 只有左子树上的部分结点 D. 只有左子树上的所有结点
13. 如图6.1所示二叉树的中序遍历序列是____。
A. abcdgef B. dfebagc C. dbaefcg D. defbagc












图6.1
14. 一棵二叉树如图6.2所示,其中序遍历的序列为__ __。
A. abdgcefh B. dgbaechf C. gdbehfca D. abcdefgh
15.设a,b为一棵二叉树上的两个结点,在中序遍历时,a在b前的条件是 。
A.a在b的右方 B.a在b的左方
C.a是b的祖先 D.a是b的子孙
16. 已知某二叉树的后序遍历序列是dabec,中序遍历序列是debac,它的前序遍历序列是____。 A. acbed B. decab C. deabc D. cedba
17. 实现任意二叉树的后序遍历的非递归算法而不使用栈结构,最佳方案是二叉树采用____存储结构。
A. 二叉链表 B. 广义表存储结构 C. 三叉链表 D. 顺序存储结构
18. 如图6.3所示的4棵二叉树,____不是完全二叉树。








19. 如图6.4所示的4棵二叉树,____是平衡二叉树。










20. 在线索化二叉树中,t所指结点没有左子树的充要条件是____。
A. t—>left=NULL B. t—>ltag=1
C. t—>ltag=1且t—>left=NULL D. 以上都不对
21. 二叉树按某种顺序线索化后,任一结点均有指向其前驱和后续的线索,这种说法____。 A. 正确 B. 错误
22. 二叉树为二叉排序树的充分必要条件是其任一结点的值均大于其左孩子的值、小于其右孩子的值。这种说法____。 A. 正确 B. 错误
23. 具有五层结点的二叉平衡树至少有____个结点。
A. 10 B. 12 C. 15 D. 17
24. 树的基本遍历策略可分为先根遍历和后根遍历;二叉树的基本遍历策略可分为先序遍历、中序遍历和后序遍历。这里,我们把由树转化得到的二叉树叫做这棵数对应的二叉树。结论____是正确的。
A.树的先根遍历序列与其对应的二叉树的先序遍历序列相同
B.树的后根遍历序列与其对应的二叉树的后序遍历序列相同
C.树的先根遍历序列与其对应的二叉树的中序遍历序列相同
D.以上都不对
25. 树最适合用来表示____。
A. 有序数据元素 B. 无序数据元素
C. 元素之间具有分支层次关系的数据 D. 元素之间无联系的数据
6.2 填空题(将正确的答案填在相应的空中)
1. 有一棵树如图6.5所示,回答下面的问题:
⑴ 这棵树的根结点是____;
⑵ 这棵树的叶子结点是____;
⑶ 结点k3的度是____;
⑷ 这棵树的度是____;
⑸ 这棵树的深度是____;
⑹ 结点k3的子女是____;
⑺ 结点k3的父结点是____;

2. 指出树和二叉树的三个主要差别____、____、____。
3. 从概念上讲,树与二叉树是两种不同的数据结构,将树转化为二叉树的基本目的是___ _。





4. 一棵二叉树的结点数据采用顺序存储结构,存储于数组t中,如图6.6所示,则该二叉树的链接表示形式为__ __。
5. 深度为k的完全二叉树至少有____个结点。至多有____个结点,若按自上而下,从左到右次序给结点编号(从1开始),则编号最小的叶子结点的编号是____。
6. 在一棵二叉树中,度为零的结点的个数为n 0,度为2的结点的个数为 n 2,则有n0=____。
7. 一棵二叉树的第i(i≥1)层最多有____个结点;一棵有n(n>0)个结点的满二叉树共有____个叶子和____个非终端结点。
8. 结点最少的树为____,结点最少的二叉树为____。
9. 现有按中序遍历二叉树的结果为abc,问有____种不同形态的二叉树可以得到这一遍历结果,这些二叉树分别是____。
10. 由如图6.7所示的二叉树,回答以下问题:
⑴ 其中序遍历序列为____;
⑵ 其前序遍历序列为____;
⑶ 其后序遍历序列为____;




6.3 简答题
1. 根据二叉树的定义,具有三个结点的二叉树有5种不同的形态,请将它们分别画出。
2. 假设一棵 二叉树的先序序列为EBADCFHGIKJ和中序序列为ABCDEFGHIJK。
请画出该树。
3. 由如图6.7所示的二叉树,回答以下问题:
(1)画出该二叉树的中序线索二叉树;
(2)画出该二叉树的后序线索二叉树;
(3)画出该二叉树对应的森林。
4. 已知一棵树如图6.8所示,转化为一棵二叉树,表示为____。



5. 以数据集{4,5,6,7,10,12,18}为结点权值,画出构造Huffman树的每一步图示,计算其带权路径长度为。
6. 一棵含有N个结点的k叉树,可能达到的最大深度和最小深度各为多少?
7. 证明:一棵满k叉树上的叶子结点数n 和非叶子结点数n 之间满足以下关系:
n =(k-1)n +1
6.4 算法设计题
1. 编写按层次顺序(同一层自左至右)遍历二叉树的算法。
2.试编写算法,对一棵二叉树,统计叶子的个数。
3.试编写算法,对一棵二叉树根结点不变,将左、右子树进行交换,树中每个结点的左、右子树进行交换。
7. 假设用于通讯的电文仅有八个字母(a,b,c,d,e,f,g,h)组成,字母在电文中出现的频率分别为0.07, 0.19, 0.02, 0.06, 0.32, 0.03, 0.21, 0.10。试为这八个字母设计哈夫曼编码。
使用0-7的二进制表示形式是另一种编码方案。对于上述实例,比较两种方案的优缺点。
8. 试编写算法,对一棵以孩子-兄弟链表表示的树统计叶子的个数。假设一棵 二叉树的先序序列为EBADCFHGIKJ和中序序列为ABCDEFGHIJK。请画出该树。
习题答案
6.1 1. B 2. B 3. C 4. C 5. C 6. A 7. D 8. A 9. C 10. A
11. D 2. A 13. B 14. B 15. B 16. D 17. C 18. C
19. B 20. B 21. B 22. B 23. B 24. A 25. C
6.2
1. ⑴ k1 ⑵ k2,k5,k7,k4 ⑶ 2 ⑷ 3 ⑸ 4 ⑹ k5,k6 ⑺ k1
2. 树的结点个数至少为1(不同教材规定不同),而二叉树的结点个数可以为0;
树中结点的最大度数没有限制,而二叉树结点的最大度数为2;
树的结点无左、右之分,而二叉树的结点有左、右之分;
3. 树可采用孩子-兄弟链表(二叉链表)做存储结构,目的并利用二叉树的已有算法解决树的有关问题。
4. 如图6.9所示
5. 2 k-1 、 2 k-1 、 2 k-2+1
6. n2+1
7. 2 i-1 2[log2n+1]-1 2[log2n+1] –1
8. 只有一个结点的树;空的二叉树
9. 5;如图6.10所示
10. dgbaechif 、abdgcefhi 、gdbeihfca 、
6.3 1. 5种, 图6.11
2. 二叉树如图6.12所示。





3. 中序线索二叉树如图6.13(左)所示;后序线索二叉树如图6.13(右)所示;
该二叉树转换后的的森林如图6.14所示。












4. 图6.8的树转化为一棵二叉树如下,图6.15:








5. 画出构造Huffman树如图6.16所示,计算其带权路径长度为 。




6. 一棵含有N个结点的k叉树,可能达到的最大深度 h=N-k+1 ,
最小深度各为: logkN+1。

习题7 图
7.1 单项选择题
1.在一个图中,所有顶点的度数之和等于所有边数的____倍。
A. 1/2 B. 1 C. 2 D. 4
2.任何一个无向连通图的最小生成树 。
A.只有一棵 B.有一棵或多棵 C.一定有多棵 D.可能不存在
3.在一个有向图中,所有顶点的入度之和等于所有顶点的出度之和的____倍。
A. 1/2 B. 1 C. 2 D. 4
4.一个有n个顶点的无向图最多有____条边。
A. n B. n(n-1) C. n(n-1)/2 D. 2n
5.具有4个顶点的无向完全图有____条边。
A. 6 B. 12 C. 16 D. 20
6.具有6个顶点的无向图至少应有____条边才能确保是一个连通图。
A. 5 B. 6 C. 7 D. 8
7.在一个具有n个顶点的无向图中,要连通全部顶点至少需要____条边。
A. n B. n+1 C. n-1 D. n/2
8.对于一个具有n个顶点的无向图,若采用邻接矩阵表示,则该矩阵的大小是____。
A. n B. (n-1)2 C. n-1 D. n2
9.对于一个具有n个顶点和e条边的无向图,若采用邻接表表示,则表头向量的大小为_①___;所有邻接表中的接点总数是_②___。
① A. n B. n+1 C. n-1 D. n+e
② A. e/2 B. e C.2e D. n+e
10.已知一个图如图7.1所示,若从顶点a出发按深度搜索法进行遍历,则可能得到
的一种顶点序列为__①__;按宽度搜索法进行遍历,则可能得到的一种顶点序列
为__②__。
① A. a,b,e,c,d,f B. e,c,f,e,b,d C. a,e,b,c,f,d D. a,e,d,f,c,b
② A. a,b,c,e,d,f B. a,b,c,e,f,d C. a,e,b,c,f,d D. a,c,f,d,e,b









11.已知一有向图的邻接表存储结构如图7.2所示。










⑴ 根据有向图的深度优先遍历算法,从顶点v1出发,所得到的顶点序列是____。
A. v1,v2,v3,v5,v4 B. v1,v2,v3,v4,v5
C. v1,v3,v4,v5,v2 D. v1,v4,v3,v5,v2
⑵ 根据有向图的宽度优先遍历算法,从顶点v1出发,所得到的顶点序列是____。
A. v1,v2,v3,v4,v5 B. v1,v3,v2,v4,v5
C. v1,v2,v3,v5,v4 D. v1,v4,v3,v5,v2
12.采用邻接表存储的图的深度优先遍历算法类似于二叉树的____。
A. 先序遍历 B. 中序遍历 C. 后序遍历 D. 按层遍历
13.采用邻接表存储的图的宽度优先遍历算法类似于二叉树的____。
A. 先序遍历 B. 中序遍历 C. 后序遍历 D. 按层遍历
14.判定一个有向图是否存在回路除了可以利用拓扑排序方法外,还可以利用____。
A. 求关键路径的方法 B. 求最短路径的Dijkstra方法
C. 宽度优先遍历算法 D. 深度优先遍历算法
15.关键路径是事件结点网络中 。
A.从源点到汇点的最长路径 B.从源点到汇点的最短路径
C.最长的回路 D.最短的回路
16.下面不正确的说法是 。
(1)在AOE网中,减小一个关键活动上的权值后,整个工期也就相应减小;
(2)AOE网工程工期为关键活动上的权之和;
(3)在关键路径上的活动都是关键活动,而关键活动也必在关键路径上。
A.(1) B.(2) C.(3) D.(1)、(2)
17.用DFS遍历一个无环有向图,并在DFS算法退栈返回时打印出相应的顶点,则输出的顶点序列是 。
A.逆拓朴有序的 B.拓朴有序的 C.无序的
18.在图7.3所示的拓朴排列的结果序列为 。
A.125634 B.516234 C.123456 D.521634


19.一个有n个顶点的无向连通图,它所包含的连通分量个数为 。
A.0 B.1 C.n D.n+1
20.对于一个有向图,若一个顶点的入度为k1,、出度为k2,则对应邻接表中该顶点单链表中的结点数为 。
A.k1 B.k2 C.k1-k2 D.k1+k2
21.对于一个有向图,若一个顶点的入度为k1,、出度为k2,则对应逆邻接表中该顶点单链表中的结点数为 。
A.k1 B.k2 C.k1-k2 D.k1+k2
7.2 填空题(将正确的答案填在相应饿空中)
1.n个顶点的连通图至少____条边。
2.在无权图G的邻接矩阵A中,若(vi,vj)或<vi,vj>属于图G的边集合,则对应元素A[i][j]等于____,否则等于____。
3.在无向图G的邻接矩阵A中,若A[i][j]等于1,则A[j][i ]等于____。
4.已知图G的邻接表如图7.4所示,其从顶点v1出发的深度有限搜索序列为____,其从顶点v1出发的宽度优先搜索序列为____。










图7.4 图G的邻接表
5.已知一个有向图的邻接矩阵表示,计算第i个结点的入度的方法是____。
6.已知一个图的邻接矩阵表示,删除所有从第i个结点出发的边的方法是____。
7.如果含n个顶点的图形成一个环,则它有 棵生成树。
8.一个非连通无向图,共有28条边,则该图至少有 个顶点。
9.遍历图的过程实质上是 。BFS遍历图的时间复杂度为 ,DFS遍历图的时间复杂度为 ,两者不同之处在于 ,反映在数据结构上的差别是 。
10.一个图的 表示法是唯一的,而 表示法是不唯一的。
11.有向图中的结点前驱后继关系的特征是 。
12.若无向图G的顶点度数最小值大于等于 时,G至少有一条回路。
13.根据图的存储结构进行某种次序的遍历,得到的顶点序列是 的。
7.3 综合题
1.已知如图7.5所示的有向图,请给出该图的:
(1)每个顶点的入/出度;
(2)邻接距阵;
(3)邻接表;
(4)逆邻接表;
(5)强连通分量。





2.请用克鲁斯卡尔和普里姆两种算法分别为图7.6、图7.7构造最小生成树:
(1)




图7.6
(2)






图7.7
3.试列出图7.8中全部的拓扑排序序列。






图7.8


4.请用图示说明图7.9从顶点a到其余各顶点之间的最短路径。








图7.9


5.已知AOE网有9个结点:V1,V2,V3,V4,V5,V6,V7,V8,V9,其邻接矩阵如下:
(1)请画出该AOE图。
(2)计算完成整个计划需要的时间。
(3)求出该AOE网的关键路径。
∝ 6 4 5 ∝ ∝ ∝ ∝ ∝
∝ ∝ ∝ ∝ 1 ∝ ∝ ∝ ∝
∝ ∝ ∝ ∝ 1 ∝ ∝ ∝ ∝
∝ ∝ ∝ ∝ ∝ 2 ∝ ∝ ∝
∝ ∝ ∝ ∝ ∝ ∝ 9 7 ∝
∝ ∝ ∝ ∝ ∝ ∝ ∝ 4 ∝
∝ ∝ ∝ ∝ ∝ ∝ ∝ ∝ 2
∝ ∝ ∝ ∝ ∝ ∝ ∝ ∝ 4
∝ ∝ ∝ ∝ ∝ ∝ ∝ ∝ ∝


习题答案
7.1 1. C 2.B 3.B 4. C 5. A 6. A 7.C
8.D 9. AC 10.DB 11. CB 12. A 13. D 14.D 15.A 16.A 17.A 18.B 19.B 20.B 21.A
7.2 1.n-1 2. 1;0 3. 1
4.v1,v2,v3,v6,v5, v4;v1,v2,v5,v4,v3, v6
5.求矩阵第i列非零元素之和
6. 将矩阵第i行全部置为零
7.n
8.9
9.对每个顶点查找其邻接点的过程;O(e)(e为图中的边数);O(e);
遍历图的顺序不同;DFS采用栈存储访问过的结点,BFS采用队列存储访问过
的结点。
10.邻接矩阵 邻接表
11.一个结点可能有若干个前驱,也可能有若干个后继
12.2
13.唯一

7.3 1.








2.
(1).









(2)







3.
152364
152634
156234
561234
516234
512634
512364
4.









5.(1)该AOE图为:

(2)完成整个计划需要18天。
(3)关键路径为:(V1,V2,V5,V7,V9)和(V1,V2, V5,V8,V9,)

习题8 查找
8.1 单项选择题
1.顺序查找法适合于存储结构为____的线性表。
A. 散列存储 B. 顺序存储或链接存储
C. 压缩存储 D. 索引存储
2.对线性表进行二分查找时,要求线性表必须____。
A. 以顺序方式存储 B. 以链接方式存储
C. 以顺序方式存储,且结点按关键字有序排序
D. 以链接方式存储,且结点按关键字有序排序
3.采用顺序查找方法查找长度为n的线性表时,每个元素的平均查找长度为____.
A. n B. n/2 C. (n+1)/2 D. (n-1)/2
4.采用二分查找方法查找长度为n的线性表时,每个元素的平均查找长度为____。
A.O(n2) B. O(nlog2n) C. O(n) D. O(log2n)
5.二分查找和二叉排序树的时间性能____。
A. 相同 B. 不相同
6.有一个有序表为{1,3,9,12,32,41,45,62,75,77,82,95,100},当二分查找值82为的结点时,____次比较后查找成功。
A. 1 B. 2 C. 4 D. 8
7.设哈希表长m=14,哈希函数H(key)=key%11。表中已有4个结点:
addr (15)=4; addr (38)=5; addr (61)=6; addr (84)=7
如用二次探测再散列处理冲突,关键字为49的结点的地址是____。
A. 8 B. 3 C. 5 D. 9
8.有一个长度为12的有序表,按二分查找法对该表进行查找,在表内各元素等概率情况下查找成功所需的平均比较次数为____。
A. 35/12 B. 37/12 C. 39/12 D. 43/12
9.对于静态表的顺序查找法,若在表头设置岗哨,则正确的查找方式为 。
A.从第0个元素往后查找该数据元素
B.从第1个元素往后查找该数据元素
C.从第n个元素往开始前查找该数据元素
D.与查找顺序无关
10.解决散列法中出现的冲突问题常采用的方法是 。
A.数字分析法、除余法、平方取中法
B.数字分析法、除余法、线性探测法
C.数字分析法、线性探测法、多重散列法
D.线性探测法、多重散列法、链地址法
11.采用线性探测法解决冲突问题,所产生的一系列后继散列地址 。
A.必须大于等于原散列地址
B.必须小于等于原散列地址
C.可以大于或小于但不能等于原散列地址
D.地址大小没有具体限制
12.对于查找表的查找过程中,若被查找的数据元素不存在,则把该数据元素插入到集合中。这种方式主要适合于 。
A.静态查找表 B.动态查找表
C.静态查找表与动态查找表 D两种表都不适合
13.散列表的平均查找长度 。
A.与处理冲突方法有关而与表的长度无关
B.与处理冲突方法无关而与表的长度有关
C.与处理冲突方法有关而与表的长度有关
D.与处理冲突方法无关而与表的长度无关
8.2 填空题(将正确的答案填在相应的空中)
1.顺序查找法的平均查找长度为____;折半查找法的平均查找长度为____;哈希表查找法采用链接法处理冲突时的平均查找长度为____。
2.在各种查找方法中,平均查找长度与结点个数n无关的查找方法是____。
3.折半查找的存储结构仅限于____,且是____。
4. 假设在有序线性表A[1..20]上进行折半查找,则比较一次查找成功的结点数为____,则比较二次查找成功的结点数为____,则比较三次查找成功的结点数为____,则比较四次查找成功的结点数为____,则比较五次查找成功的结点数为____,平均查找长度为____。
5. 对于长度为n的线性表,若进行顺序查找,则时间复杂度为____;若采用折半法查找,则时间复杂度为____;
6.已知有序表为(12,18,24,35,47,50,62,83,90,115,134),当用折半查找90时,需进行 次查找可确定成功;查找47时,需进行 次查找成功;查找100时,需进行 次查找才能确定不成功。
7.二叉排序树的查找长度不仅与 有关,也与二叉排序树的 有关。
8.一个无序序列可以通过构造一棵 树而变成一个有序树,构造树的过程即为对无序序列进行排序的过程。
9.平衡二叉排序树上任一结点的平衡因子只可能是 、 或 。
10. 法构造的哈希函数肯定不会发生冲突。
11.在散列函数H(key)=key%p中,p应取____。
12.在散列存储中,装填因子 的值越大,则____; 的值越小,则____。
8.3 综合练习题:
1. 画出对长度为10的有序表进行折半查找的判定树,并求其等概率时查找成功的平均查找长度。
2.含九个叶子结点的3阶B-树中至少有多少个非叶子结点?含10个叶子结点的3阶B-树中至多有多少个非叶子结点?
3.试从空树开始,画出按以下次序向2-3树即3阶B-树中插入关键码的建树过程:20,30,50,52,60,68,70.如果此后删除50和68,画出每一步执行后2-3树的状态。
4. 选取哈稀函数H(k)=(3k)MOD 11。用开放定址法处理冲突,di=i((7k)MOD 10+1)(I=1,2,3,…).试在0-10的散列地址空间中对关键字序列(22,41,53,46,30,13,01,67)造哈希表,并求等概率情况下查找成功时的平均查找长度。
5. 已知一组关键字{49,38,65,97,76,13,27,44,82,35,50},画出由此生成的二叉排序树,注意边插入边平衡。
习题答案
8.1 1.B 2.C 3.C 4.D 5.B 6.C 7.D 8.B
9.C 10.D 11.C 12.B 13.C

8.2 1. (n+1)/2 、((n+1)*log2(n+1))/n-1 、1+ ( 为装填因子)
2. 哈希表查找法
3. 顺序存储结构、有序的
4. 1、2、4、8、5、3.7
(依题意,构造一棵有序二叉树,共12个结点,第一层1个结点,第二层2个结点,第三层4个结点,第四层5个结点,则:ASL=(1*1+2*2+3*4+4*5)/12=37/12)
5. O(n)、O(log2n)
6.2、4、3
7.结点个数n、生成过程
8.二叉排序树
9.0、1、-1
10.直接定址
11.素数
12.存取元素时发生冲突的可能性就越大、存取元素时发生冲突的可能性就越小




习题9 排序
9.1 单项选择题
1. 在所有排序方法中,关键字比较的次数与记录的初始排列次序无关的是____。
A. 希尔排序 B. 起泡排序 C. 插入排序 D. 选择排序
2. 设有1000个无序的元素,希望用最快的速度挑选出其中前10个最大的元素,最好选用____排序法。
A. 起泡排序 B. 快速排序 C. 堆排序 D. 基数排序
3. 在待排序的元素序列基本有序的前提下,效率最高的排序方法是____。
A. 插入排序 B. 选择排序
文件为doc版,可自行转成txt,在手机上看挺好的。 本资源来自网络,如有纰漏还请告知,如觉得还不错,请留言告知后来人,谢谢!!!!! 入门学习Linux常用必会60个命令实例详解 Linux必学的60个命令 Linux提供了大量的命令,利用它可以有效地完成大量的工作,如磁盘操作、文件存取、目录操作、进程管理、文件权限设定等。所以,在Linux系统上工作离不开使用系统提供的命令。要想真正理解Linux系统,就必须从Linux命令学起,通过基础的命令学习可以进一步理解Linux系统。 不同Linux发行版的命令数量不一样,但Linux发行版本最少的命令也有200多个。这里笔者把比较重要和使用频率最多的命令,按照它们在系统中的作用分成下面六个部分一一介绍。 ◆ 安装和登录命令:login、shutdown、halt、reboot、install、mount、umount、chsh、exit、last; ◆ 文件处理命令:file、mkdir、grep、dd、find、mv、ls、diff、cat、ln; ◆ 系统管理相关命令:df、top、free、quota、at、lp、adduser、groupadd、kill、crontab; ◆ 网络操作命令:ifconfig、ip、ping、netstat、telnet、ftp、route、rlogin、rcp、finger、mail、 nslookup; ◆ 系统安全相关命令:passwd、su、umask、chgrp、chmod、chown、chattr、sudo ps、who; ◆ 其它命令:tar、unzip、gunzip、unarj、mtools、man、unendcode、uudecode。 本文以Mandrake Linux 9.1(Kenrel 2.4.21)为例,介绍Linux下的安装和登录命令。 immortality按:请用ctrl+f在本页中查找某一部分的内容或某一命令的用法。 -------------------------------------------------------------------------------- Linux必学的60个命令(1)-安装与登陆命令 login 1.作用 login的作用是登录系统,它的使用权限是所有用户。 2.格式 login [name][-p ][-h 主机名称] 3.主要参数 -p:通知login保持现在的环境参数。 -h:用来向远程登录的之间传输用户名。 如果选择用命令行模式登录Linux的话,那么看到的第一个Linux命令就是login:。 一般界面是这样的: Manddrake Linux release 9.1(Bamboo) for i586 renrel 2.4.21-0.13mdk on i686 / tty1 localhost login:root password: 上面代码中,第一行是Linux发行版本号,第二行是内核版本号和登录的虚拟控制台,我们在第三行输入登录名,按“Enter”键在Password后输入账户密码,即可登录系统。出于安全考虑,输入账户密码时字符不会在屏幕上回显,光标也不移动。 登录后会看到下面这个界面(以超级用户为例): [root@localhost root]# last login:Tue ,Nov 18 10:00:55 on vc/1 上面显示的是登录星期、月、日、时间和使用的虚拟控制台。 4.应用技巧 Linux 是一个真正的多用户操作系统,可以同时接受多个用户登录,还允许一个用户进行多次登录。这是因为Linux和许多版本的Unix一样,提供了虚拟控制台的访问方式,允许用户在同一时间从控制台(系统的控制台是与系统直接相连的监视器和键盘)进行多次登录。每个虚拟控制台可以看作是一个独立的工作站,工作台之间可以切换。虚拟控制台的切换可以通过按下Alt键和一个功能键来实现,通常使用F1-F6 。 例如,用户登录后,按一下“Alt+ F2”键,用户就可以看到上面出现的“login:”提示符,说明用户看到了第二个虚拟控制台。然后只需按“Alt+ F1”键,就可以回到第一个虚拟控制台。一个新安装的Linux系统允许用户使用“Alt+F1”到“Alt+F6”键来访问前六个虚拟控制台。虚拟控制台最有用的是,当一个程序出错造成系统死锁时,可以切换到其它虚拟控制台工作,关闭这个程序。 shutdown 1.作用 shutdown命令的作用是关闭计算机,它的使用权限是超级用户。 2.格式 shutdown [-h][-i][-k][-m][-t] 3.重要参数 -t:在改变到其它运行级别之前,告诉init程序多久以后关机。 -k:并不真正关机,只是送警告信号给每位登录者。 -h:关机后关闭电源。 -c:cancel current process取消目前正在执行的关机程序。所以这个选项当然没有时间参数,但是可以输入一个用来解释的讯息,而这信息将会送到每位使用者。 -F:在重启计算机时强迫fsck。 -time:设定关机前的时间。 -m: 将系统改为单用户模式。 -i:关机时显示系统信息。 4.命令说明 shutdown 命令可以安全地将系统关机。有些用户会使用直接断掉电源的方式来关闭Linux系统,这是十分危险的。因为Linux与Windows不同,其后台运行着许多进程,所以强制关机可能会导致进程的数据丢失,使系统处于不稳定的状态,甚至在有的系统中会损坏硬件设备(硬盘)。在系统关机前使用 shutdown命令,系统管理员会通知所有登录的用户系统将要关闭,并且login指令会被冻结,即新的用户不能再登录。 halt 1.作用 halt命令的作用是关闭系统,它的使用权限是超级用户。 2.格式 halt [-n] [-w] [-d] [-f] [-i] [-p] 3.主要参数说明 -n:防止sync系统调用,它用在用fsck修补根分区之后,以阻止内核用老版本的超级块覆盖修补过的超级块。 -w:并不是真正的重启或关机,只是写wtmp(/var/log/wtmp)纪录。 -f:没有调用shutdown,而强制关机或重启。 -i:关机(或重启)前,关掉所有的网络接口。 -f:强迫关机,不呼叫shutdown这个指令。 -p: 当关机的时候顺便做关闭电源的动作。 -d:关闭系统,但不留下纪录。  4.命令说明 halt 就是调用shutdown -h。halt执行时,杀死应用进程,执行sync(将存于buffer中的资料强制写入硬盘中)系统调用,文件系统写操作完成后就会停止内核。若系统的运行级别为0或6,则关闭系统;否则以shutdown指令(加上-h参数)来取代。  reboot 1.作用 reboot命令的作用是重新启动计算机,它的使用权限是系统管理者。 2.格式 reboot [-n] [-w] [-d] [-f] [-i] 3.主要参数 -n: 在重开机前不做将记忆体资料写回硬盘的动作。 -w: 并不会真的重开机,只是把记录写到/var/log/wtmp文件里。 -d: 不把记录写到/var/log/wtmp文件里(-n这个参数包含了-d)。 -i: 在重开机之前先把所有与网络相关的装置停止。 install 1.作用 install命令的作用是安装或升级软件或备份数据,它的使用权限是所有用户。 2.格式 (1)install [选项]... 来源 目的地 (2)install [选项]... 来源... 目录 (3)install -d [选项]... 目录... 在前两种格式中,会将复制至或将多个文件复制至已存在的,同时设定权限模式及所有者/所属组。在第三种格式中,会创建所有指定的目录及它们的主目录。长选项必须用的参数在使用短选项时也是必须的。 3.主要参数 --backup[=CONTROL]:为每个已存在的目的地文件进行备份。 -b:类似 --backup,但不接受任何参数。 -c:(此选项不作处理)。 -d,--directory:所有参数都作为目录处理,而且会创建指定目录的所有主目录。 -D:创建前的所有主目录,然后将复制至 ;在第一种使用格式中有用。 -g,--group=组:自行设定所属组,而不是进程目前的所属组。 -m,--mode=模式:自行设定权限模式 (像chmod),而不是rwxr-xr-x。 -o,--owner=所有者:自行设定所有者 (只适用于超级用户)。 -p,--preserve-timestamps:以文件的访问/修改时间作为相应的目的地文件的时间属性。 -s,--strip:用strip命令删除symbol table,只适用于第一及第二种使用格式。 -S,--suffix=后缀:自行指定备份文件的。 -v,--verbose:处理每个文件/目录时印出名称。 --help:显示此帮助信息并离开。 --version:显示版本信息并离开。 mount 1.作用 mount命令的作用是加载文件系统,它的用权限是超级用户或/etc/fstab中允许的使用者。 2.格式 mount -a [-fv] [-t vfstype] [-n] [-rw] [-F] device dir 3.主要参数 -h:显示辅助信息。 -v:显示信息,通常和-f用来除错。 -a:将/etc/fstab中定义的所有文件系统挂上。 -F:这个命令通常和-a一起使用,它会为每一个mount的动作产生一个行程负责执行。在系统需要挂上大量NFS文件系统时可以加快加载的速度。 -f:通常用于除错。它会使mount不执行实际挂上的动作,而是模拟整个挂上的过程,通常会和-v一起使用。 -t vfstype:显示被加载文件系统的类型。 -n:一般而言,mount挂上后会在/etc/mtab中写入一笔资料,在系统中没有可写入文件系统的情况下,可以用这个选项取消这个动作。 4.应用技巧 在Linux 和Unix系统上,所有文件都是作为一个大型树(以/为根)的一部分访问的。要访问CD-ROM上的文件,需要将CD-ROM设备挂装在文件树中的某个挂装点。如果发行版安装了自动挂装包,那么这个步骤可自动进行。在Linux中,如果要使用硬盘、光驱等储存设备,就得先将它加载,当储存设备挂上了之后,就可以把它当成一个目录来访问。挂上一个设备使用mount命令。在使用mount这个指令时,至少要先知道下列三种信息:要加载对象的文件系统类型、要加载对象的设备名称及要将设备加载到哪个目录下。 (1)Linux可以识别的文件系统 ◆ Windows 95/98常用的FAT 32文件系统:vfat ; ◆ Win NT/2000 的文件系统:ntfs ; ◆ OS/2用的文件系统:hpfs; ◆ Linux用的文件系统:ext2、ext3; ◆ CD-ROM光盘用的文件系统:iso9660。 虽然vfat是指FAT 32系统,但事实上它也兼容FAT 16的文件系统类型。 (2)确定设备的名称 在Linux 中,设备名称通常都存在/dev里。这些设备名称的命名都是有规则的,可以用“推理”的方式把设备名称找出来。例如,/dev/hda1这个 IDE设备,hd是Hard Disk(硬盘)的,sd是SCSI Device,fd是Floppy Device(或是Floppy Disk?)。a代表第一个设备,通常IDE接口可以接上4个IDE设备(比如4块硬盘)。所以要识别IDE硬盘的方法分别就是hda、hdb、hdc、 hdd。hda1中的“1”代表hda的第一个硬盘分区 (partition),hda2代表hda的第二主分区,第一个逻辑分区从hda5开始,依此类推。此外,可以直接检查 /var/log/messages文件,在该文件中可以找到计算机开机后系统已辨认出来的设备代号。 (3)查找挂接点 在决定将设备挂接之前,先要查看一下计算机是不是有个/mnt的空目录,该目录就是专门用来当作挂载点(Mount Point)的目录。建议在/mnt里建几个/mnt/cdrom、/mnt/floppy、/mnt/mo等目录,当作目录的专用挂载点。举例而言,如要挂载下列5个设备,其执行指令可能如下 (假设都是Linux的ext2系统,如果是Windows XX请将ext2改成vfat): 软盘 ===>mount -t ext2 /dev/fd0 /mnt/floppy cdrom ===>mount -t iso9660 /dev/hdc /mnt/cdrom SCSI cdrom ===>mount -t iso9660 /dev/sdb /mnt/scdrom SCSI cdr ===>mount -t iso9660 /dev/sdc /mnt/scdr 不过目前大多数较新的Linux发行版本(包括红旗 Linux、中软Linux、Mandrake Linux等)都可以自动挂装文件系统,但Red Hat Linux除外。 umount 1.作用 umount命令的作用是卸载一个文件系统,它的使用权限是超级用户或/etc/fstab中允许的使用者。 2.格式 unmount -a [-fFnrsvw] [-t vfstype] [-n] [-rw] [-F] device dir 3.使用说明 umount 命令是mount命令的逆操作,它的参数和使用方法和mount命令是一样的。Linux挂装CD-ROM后,会锁定CD—ROM,这样就不能用CD- ROM面板上的Eject按钮弹出它。但是,当不再需要光盘时,如果已将/cdrom作为符号链接,请使用umount/cdrom来卸装它。仅当无用户正在使用光盘时,该命令才会成功。该命令包括了将带有当前工作目录当作该光盘中的目录的终端窗口。 chsh 1.作用 chsh命令的作用是更改使用者shell设定,它的使用权限是所有使用者。 2.格式 chsh [ -s ] [ -list] [ --help ] [ -v ] [ username ] 3.主要参数 -l:显示系统所有Shell类型。 -v:显示Shell版本号。 4.应用技巧 前面介绍了Linux下有多种Shell,一般缺省的是Bash,如果想更换Shell类型可以使用chsh命令。先输入账户密码,然后输入新Shell类型,如果操作正确系统会显示“Shell change”。其界面一般如下: Changing fihanging shell for cao Password: New shell [/bin/bash]: /bin/tcsh 上面代码中,[ ]内是目前使用的Shell。普通用户只能修改自己的Shell,超级用户可以修改全体用户的Shell。要想查询系统提供哪些Shell,可以使用chsh -l 命令,见图1所示。 图1 系统可以使用的Shell类型 从图1中可以看到,笔者系统中可以使用的Shell有bash(缺省)、csh、sh、tcsh四种。 exit 1.作用 exit命令的作用是退出系统,它的使用权限是所有用户。 2.格式 exit 3.参数 exit命令没有参数,运行后退出系统进入登录界面。 last 1.作用 last命令的作用是显示近期用户或终端的登录情况,它的使用权限是所有用户。通过last命令查看该程序的log,管理员可以获知谁曾经或企图连接系统。 2.格式 1ast[—n][-f file][-t tty] [—h 节点][-I —IP][—1][-y][1D] 3.主要参数 -n:指定输出记录的条数。 -f file:指定用文件file作为查询用的log文件。 -t tty:只显示指定的虚拟控制台上登录情况。 -h 节点:只显示指定的节点上的登录情况。 -i IP:只显示指定的IP上登录的情况。 -1:用IP来显示远端地址。 -y:显示记录的年、月、日。 -ID:知道查询的用户名。 -x:显示系统关闭、用户登录和退出的历史。 动手练习 上面介绍了Linux安装和登录命令,下面介绍几个实例,动手练习一下刚才讲过的命令。 1.一次运行多个命令 在一个命令行中可以执行多个命令,用分号将各个命令隔开即可,例如: #last -x;halt 上面代码表示在显示系统关闭、用户登录和退出的历史后关闭计算机。 2.利用mount挂装文件系统访问Windows系统 许多Linux发行版本现在都可以自动加载Vfat分区来访问Windows系统,而Red Hat各个版本都没有自动加载Vfat分区,因此还需要进行手工操作。 mount 可以将Windows分区作为Linux的一个“文件”挂接到Linux的一个空文件夹下,从而将Windows的分区和/mnt这个目录联系起来。因此,只要访问这个文件夹就相当于访问该分区了。首先要在/mnt下建立winc文件夹,在命令提示符下输入下面命令: #mount -t vfat /dev/hda1 /mnt/winc 即表示将Windows的C分区挂到Liunx的/mnt/winc目录下。这时,在/mnt/winc目录下就可以看到Windows中C盘的内容了。使用类似的方法可以访问Windows系统的D、E盘。在Linux系统显示Windows的分区一般顺序这样的:hda1为C盘、hda5为D盘、 hda6为E盘……以此类推。上述方法可以查看Windows系统有一个很大的问题,就是Windows中的所有中文文件名或文件夹名全部显示为问号 “?”,而英文却可以正常显示。我们可以通过加入一些参数让它显示中文。还以上面的操作为例,此时输入命令: #mount -t vfat -o iocharset=cp936 /dev/hda1 /mnt/winc 现在它就可以正常显示中文了。 3.使用mount加挂闪盘上的文件系统 在Linux下使用闪盘非常简单。Linux对USB设备有很好的支持,当插入闪盘后,闪盘被识别为一个SCSI盘,通常输入以下命令: # mount /dev/sda1 /usb 就能够加挂闪盘上的文件系统。 小知识 Linux命令与Shell 所谓Shell,就是命令解释程序,它提供了程序设计接口,可以使用程序来编程。学习Shell对于Linux初学者理解Linux系统是非常重要的。 Linux系统的Shell作为操作系统的外壳,为用户提供了使用操作系统的接口。Shell是命令语言、命令解释程序程序设计语言的统称,是用户和 Linux内核之间的接口程序。如果把Linux内核想象成一个球体的中心,Shell就是围绕内核的外层。当从Shell或其它程序向Linux传递命令时,内核会做出相应的反应。Shell在Linux系统的作用和MS DOS下的COMMAND.COM和Windows 95/98 的 explorer.exe相似。Shell虽然不是系统核心的一部分,只是系统核心的一个外延,但它能够调用系统内核的大部分功能。因此,可以说 Shell是Unux/Linux最重要的实用程序。 Linux中的Shell有多种类型,其中最常用的是Bourne Shell(sh)、C Shell(csh)和Korn Shell(ksh)。大多数Linux发行版本缺省的Shell是Bourne Again Shell,它是Bourne Shell的扩展,简称bash,与Bourne Shell完全向后兼容,并且在Bourne Shell的基础上增加了很多特性。bash放在/bin/bash中,可以提供如命令补全、命令编辑和命令历史表等功能。它还包含了很多C Shell和Korn Shell中的优点,有灵活和强大的编程接口,同时又有很友好的用户界面。Linux系统中200多个命令中有40个是bash的内部命令,主要包括 exit、less、lp、kill、 cd、pwd、fc、fg等。 -------------------------------------------------------------------------------- Linux必学的60个命令(2)-文件处理命令 Linux 系统信息存放在文件里,文件与普通的公务文件类似。每个文件都有自己的名字、内容、存放地址及其它一些管理信息,如文件的用户、文件的大小等。文件可以是一封信、一个通讯录,或者是程序的源语句、程序的数据,甚至可以包括可执行的程序和其它非正文内容。 Linux文件系统具有良好的结构,系统提供了很多文件处理程序。这里主要介绍常用的文件处理命令。 file 1.作用 件内容判断文件类型,使用权限是所有用户。 2.格式 file通过探测文 file [options] 文件名 3.[options]主要参数 -v:在标准输出后显示版本信息,并且退出。 -z:探测压缩过的文件类型。 -L:允许符合连接。 -f name:从文件namefile中读取要分析的文件名列表。 4.简单说明 使用file命令可以知道某个文件究竟是二进制(ELF格式)的可执行文件, 还是Shell Script文件,或者是其它的什么格式。file能识别的文件类型有目录、Shell脚本、英文文本、二进制可执行文件、C语言源文件、文本文件、DOS的可执行文件。 5.应用实例 如果我们看到一个没有后缀的文件grap,可以使用下面命令: $ file grap grap: English text 此时系统显示这是一个英文文本文件。需要说明的是,file命令不能探测包括图形、音频、视频等多媒体文件类型。 mkdir 1.作用 mkdir命令的作用是建立名称为dirname的子目录,与MS DOS下的md命令类似,它的使用权限是所有用户。 2.格式 mkdir [options] 目录名 3.[options]主要参数 -m, --mode=模式:设定权限,与chmod类似。 -p, --parents:需要时创建上层目录;如果目录早已存在,则不当作错误。 -v, --verbose:每次创建新目录都显示信息。 --version:显示版本信息后离开。 4.应用实例 在进行目录创建时可以设置目录的权限,此时使用的参数是“-m”。假设要创建的目录名是“tsk”,让所有用户都有rwx(即读、写、执行的权限),那么可以使用以下命令: $ mkdir -m 777 tsk grep 1.作用 grep命令可以指定文件中搜索特定的内容,并将含有这些内容的行标准输出。grep全称是Global Regular Expression Print,表示全局正则表达式版本,它的使用权限是所有用户。 2.格式 grep [options] 3.主要参数 [options]主要参数: -c:只输出匹配行的计数。 -I:不区分大小写(只适用于单字符)。 -h:查询多文件时不显示文件名。 -l:查询多文件时只输出包含匹配字符的文件名。 -n:显示匹配行及行号。 -s:不显示不存在或无匹配文本的错误信息。 -v:显示不包含匹配文本的所有行。 pattern正则表达式主要参数: \:忽略正则表达式中特殊字符的原有含义。 ^:匹配正则表达式的开始行。 $: 匹配正则表达式的结束行。 \:到匹配正则表达式的行结束。 [ ]:单个字符,如[A]即A符合要求 。 [ - ]:范围,如[A-Z],即A、B、C一直到Z都符合要求 。 。:所有的单个字符。 * :有字符,长度可以为0。 正则表达式是Linux/Unix系统中非常重要的概念。正则表达式(也称为“regex”或“regexp”)是一个可以描述一类字符串的模式(Pattern)。如果一个字符串可以用某个正则表达式来描述,我们就说这个字符和该正则表达式匹配(Match)。这和DOS中用户可以使用通配符 “*”代表任意字符类似。在Linux系统上,正则表达式通常被用来查找文本的模式,以及对文本执行“搜索-替换”操作和其它功能。 4.应用实例 查询DNS服务是日常工作之一,这意味着要维护覆盖不同网络的大量IP地址。有时IP地址会超过2000个。如果要查看nnn.nnn网络地址,但是却忘了第二部分中的其余部分,只知到有两个句点,例如nnn nn..。要抽取其中所有nnn.nnn IP地址,使用[0-9 ]\{3 \}\.[0-0\{3\}\。含义是任意数字出现3次,后跟句点,接着是任意数字出现3次,后跟句点。 $grep '[0-9 ]\{3 \}\.[0-0\{3\}\' ipfile 补充说明,grep家族还包括fgrep和egrep。fgrep是fix grep,允许查找字符串而不是一个模式;egrep是扩展grep,支持基本及扩展的正则表达式,但不支持\q模式范围的应用及与之相对应的一些更加规范的模式。 dd 1.作用 dd命令用来复制文件,并根据参数将数据转换和格式化。 2.格式 dd [options] 3.[opitions]主要参数 bs=字节:强迫 ibs=及obs=。 cbs=字节:每次转换指定的。 conv=关键字:根据以逗号分隔的关键字表示的方式来转换文件。 count=块数目:只复制指定的输入数据。 ibs=字节:每次读取指定的。 if=文件:读取内容,而非标准输入的数据。 obs=字节:每次写入指定的。 of=文件:将数据写入,而不在标准输出显示。 seek=块数目:先略过以obs为单位的指定的输出数据。 skip=块数目:先略过以ibs为单位的指定的输入数据。 4.应用实例 dd命令常常用来制作Linux启动盘。先找一个可引导内核,令它的根设备指向正确的根分区,然后使用dd命令将其写入软盘: $ rdev vmlinuz /dev/hda $dd if=vmlinuz of=/dev/fd0 上面代码说明,使用rdev命令将可引导内核vmlinuz中的根设备指向/dev/hda,请把“hda”换成自己的根分区,接下来用dd命令将该内核写入软盘。 find 1.作用 find命令的作用是在目录中搜索文件,它的使用权限是所有用户。 2.格式 find [path][options][expression] path指定目录路径,系统从这里开始沿着目录树向下查找文件。它是一个路径列表,相互用空格分离,如果不写path,那么默认为当前目录。 3.主要参数 [options]参数: -depth:使用深度级别的查找过程方式,在某层指定目录中优先查找文件内容。 -maxdepth levels:表示至多查找到开始目录的第level层子目录。level是一个非负数,如果level是0的话表示仅在当前目录中查找。 -mindepth levels:表示至少查找到开始目录的第level层子目录。 -mount:不在其它文件系统(如Msdos、Vfat等)的目录和文件中查找。 -version:打印版本。 [expression]是匹配表达式,是find命令接受的表达式,find命令的所有操作都是针对表达式的。它的参数非常多,这里只介绍一些常用的参数。 —name:支持统配符*和?。 -atime n:搜索在过去n天读取过的文件。 -ctime n:搜索在过去n天修改过的文件。 -group grpoupname:搜索所有组为grpoupname的文件。 -user 用户名:搜索所有文件属主为用户名(ID或名称)的文件。 -size n:搜索文件大小是n个block的文件。 -print:输出搜索结果,并且打印。 4.应用技巧 find命令查找文件的几种方法: (1)根据文件名查找 例如,我们想要查找一个文件名是lilo.conf的文件,可以使用如下命令: find / -name lilo.conf find命令后的“/”表示搜索整个硬盘。 (2)快速查找文件 根据文件名查找文件会遇到一个实际问题,就是要花费相当长的一段时间,特别是大型Linux文件系统和大容量硬盘文件放在很深的子目录中时。如果我们知道了这个文件存放在某个目录中,那么只要在这个目录中往下寻找就能节省很多时间。比如smb.conf文件,从它的文件后缀“.conf”可以判断这是一个配置文件,那么它应该在/etc目录内,此时可以使用下面命令: find /etc -name smb.conf 这样,使用“快速查找文件”方式可以缩短时间。 (3)根据部分文件名查找方法 有时我们知道只某个文件包含有abvd这4个字,那么要查找系统中所有包含有这4个字符的文件可以输入下面命令: find / -name '*abvd*' 输入这个命令以后,Linux系统会将在/目录中查找所有的包含有abvd这4个字符的文件(其中*是通配符),比如abvdrmyz等符合条件的文件都能显示出来。 (4) 使用混合查找方式查找文件 find命令可以使用混合查找的方法,例如,我们想在/etc目录中查找大于500000字节,并且在24小时内修改的某个文件,则可以使用-and (与)把两个查找参数链接起来组合成一个混合的查找方式。 find /etc -size +500000c -and -mtime +1 mv 1.作用 mv命令用来为文件或目录改名,或者将文件由一个目录移入另一个目录中,它的使用权限是所有用户。该命令如同DOS命令中的ren和move的组合。 2.格式 mv[options] 源文件或目录 目标文件或目录 3.[options]主要参数 -i:交互方式操作。如果mv操作将导致对已存在的目标文件的覆盖,此时系统询问是否重写,要求用户回答“y”或“n”,这样可以避免误覆盖文件。 -f:禁止交互操作。mv操作要覆盖某个已有的目标文件时不给任何指示,指定此参数后i参数将不再起作用。 4.应用实例 (1)将/usr/cbu中的所有文件移到当前目录(用“.”表示)中: $ mv /usr/cbu/ * . (2)将文件cjh.txt重命名为wjz.txt: $ mv cjh.txt wjz.txt  ls 1.作用 ls命令用于显示目录内容,类似DOS下的dir命令,它的使用权限是所有用户。 2.格式 ls [options][filename] 3.options主要参数 -a, --all:不隐藏任何以“.” 字符开始的项目。 -A, --almost-all:列出除了“ . ”及 “.. ”以外的任何项目。 --author:印出每个文件著作者。 -b, --escape:以八进制溢出序列表示不可打印的字符。 --block-size=大小:块以指定的字节为单位。 -B, --ignore-backups:不列出任何以 ~ 字符结束的项目。 -f:不进行排序,-aU参数生效,-lst参数失效。 -F, --classify:加上文件类型的指示符号 (*/=@| 其中一个)。 -g:like -l, but do not list owner。 -G, --no-group:inhibit display of group information。 -i, --inode:列出每个文件的inode号。 -I, --ignore=样式:不印出任何符合Shell万用字符的项目。 -k:即--block-size=1K。 -l:使用较长格式列出信息。 -L, --dereference:当显示符号链接的文件信息时,显示符号链接所指示的对象,而并非符号链接本身的信息。 -m:所有项目以逗号分隔,并填满整行行宽。 -n, --numeric-uid-gid:类似-l,但列出UID及GID号。 -N, --literal:列出未经处理的项目名称,例如不特别处理控制字符。 -p, --file-type:加上文件类型的指示符号 (/=@| 其中一个)。 -Q, --quote-name:将项目名称括上双引号。 -r, --reverse:依相反次序排列。 -R, --recursive:同时列出所有子目录层。 -s, --size:以块大小为序。 4.应用举例 ls 命令是Linux系统使用频率最多的命令,它的参数也是Linux命令中最多的。使用ls命令时会有几种不同的颜色,其中蓝色表示是目录,绿色表示是可执行文件,红色表示是压缩文件,浅蓝色表示是链接文件,加粗的黑色表示符号链接,灰色表示是其它格式文件。ls最常使用的是ls- l,见图1所示。 图1 使用ls-l命令 文件类型开头是由10个字符构成的字符串。其中第一个字符表示文件类型,它可以是下述类型之一:-(普通文件)、d(目录)、l(符号链接)、b(块设备文件)、c(字符设备文件)。后面的9个字符表示文件的访问权限,分为3组,每组3位。第一组表示文件属主的权限,第二组表示同组用户的权限,第三组表示其他用户的权限。每一组的三个字符分别表示对文件的读(r)、写(w)和执行权限(x)。对于目录,表示进入权限。s表示当文件被执行时,把该文件的UID 或GID赋予执行进程的UID(用户ID)或GID(组 ID)。t表示设置标志位(留在内存,不被换出)。如果该文件是目录,那么在该目录中的文件只能被超级用户、目录拥有者或文件属主删除。如果它是可执行文件,那么在该文件执行后,指向其正文段的指针仍留在内存。这样再次执行它时,系统就能更快地装入该文件。接着显示的是文件大小、生成时间、文件或命令名称。 diff 1.作用 diff命令用于两个文件之间的比较,并指出两者的不同,它的使用权限是所有用户。 2.格式 diff [options] 源文件 目标文件 3.[options]主要参数 -a:将所有文件当作文本文件来处理。 -b:忽略空格造成的不同。 -B:忽略空行造成的不同。 -c:使用纲要输出格式。 -H:利用试探法加速对大文件的搜索。 -I:忽略大小写的变化。 -n --rcs:输出RCS格式。 cmp 1.作用 cmp(“compare”的缩写)命令用来简要指出两个文件是否存在差异,它的使用权限是所有用户。 2.格式 cmp[options] 文件名 3.[options]主要参数 -l: 将字节以十进制的方式输出,并方便将两个文件中不同的以八进制的方式输出。 cat 1.作用 cat(“concatenate”的缩写)命令用于连接并显示指定的一个和多个文件的有关信息,它的使用权限是所有用户。 2.格式 cat [options] 文件1 文件2…… 3.[options]主要参数 -n:由第一行开始对所有输出的行数编号。 -b:和-n相似,只不过对于空白行不编号。 -s:当遇到有连续两行以上的空白行时,就代换为一行的空白行。 4.应用举例 (1)cat命令一个最简单的用处是显示文本文件的内容。例如,我们想在命令行看一下README文件的内容,可以使用命令: $ cat README  (2)有时需要将几个文件处理成一个文件,并将这种处理的结果保存到一个单独的输出文件。cat命令在其输入上接受一个或多个文件,并将它们作为一个单独的文件打印到它的输出。例如,把README和INSTALL的文件内容加上行号(空白行不加)之后,将内容附加到一个新文本文件File1 中: $ cat README INSTALL File1 (3)cat还有一个重要的功能就是可以对行进行编号,见图2所示。这种功能对于程序文档的编制,以及法律和科学文档的编制很方便,打印在左边的行号使得参考文档的某一部分变得容易,这些在编程、科学研究、业务报告甚至是立法工作中都是非常重要的。 图2 使用cat命令/etc/named.conf文件进行编号 对行进行编号功能有-b(只能对非空白行进行编号)和-n(可以对所有行进行编号)两个参数: $ cat -b /etc/named.conf ln 1.作用 ln命令用来在文件之间创建链接,它的使用权限是所有用户。 2.格式 ln [options] 源文件 [链接名] 3.参数 -f:链结时先将源文件删除。 -d:允许系统管理者硬链结自己的目录。 -s:进行软链结(Symbolic Link)。 -b:将在链结时会被覆盖或删除的文件进行备份。 链接有两种,一种被称为硬链接(Hard Link),另一种被称为符号链接(Symbolic Link)。默认情况下,ln命令产生硬链接。 硬连接指通过索引节点来进行的连接。在Linux的文件系统中,保存在磁盘分区中的文件不管是什么类型都给它分配一个编号,称为索引节点号(Inode Index)。在Linux中,多个文件名指向同一索引节点是存在的。一般这种连接就是硬连接。硬连接的作用是允许一个文件拥有多个有效路径名,这样用户就可以建立硬连接到重要文件,以防止“误删”的功能。其原因如上所述,因为对应该目录的索引节点有一个以上的连接。只删除一个连接并不影响索引节点本身和其它的连接,只有当最后一个连接被删除后,文件的数据块及目录的连接才会被释放。也就是说,文件才会被真正删除。 与硬连接相对应,Lnux系统中还存在另一种连接,称为符号连接(Symbilc Link),也叫软连接。软链接文件有点类似于Windows的快捷方式。它实际上是特殊文件的一种。在符号连接中,文件实际上是一个文本文件,其中包含的有另一文件的位置信息。 动手练习 上面我们介绍了Linux文件处理命令,下面介绍几个实例,大家可以动手练习一下刚才讲过的命令。 1.利用符号链接快速访问关键目录 符号链接是一个非常实用的功能。假设有一些目录或文件需要频繁使用,但由于Linux的文件和目录结构等原因,这个文件或目录在很深的子目录中。比如, Apache Web服务器文档位于系统的/usr/local/httpd/htdocs中,并且不想每次都要从主目录进入这样一个长的路径之中(实际上,这个路径也非常不容易记忆)。 为了解决这个问题,可以在主目录中创建一个符号链接,这样在需要进入该目录时,只需进入这个链接即可。 为了能方便地进入Web服务器(/usr/local/httpd/htdocs)文档所在的目录,在主目录下可以使用以下命令: $ ln -s /usr/local/httpd/htdocs gg 这样每次进入gg目录就可访问Web服务器的文档,以后如果不再访问Web服务器的文档时,删除gg即可,而真正的Web服务器的文档并没有删除。 2.使用dd命令将init.rd格式的root.ram内容导入内存 dd if=/dev/fd0 of=floppy.fd dd if=root.ram of=/dev/ram0 # 3.grep命令系统调用 grep是Linux/Unix中使用最广泛的命令之一,许多Linux系统内部都可以调用它。 (1)如果要查询目录列表中的目录,方法如下: $ ls -l | grep '∧d' (2)如果在一个目录中查询不包含目录的所有文件,方法如下: $ ls -l | grep '∧[∧d]' (3)用find命令调用grep,如所有C源代码中的“Chinput”,方法如下: $find /ZhXwin -name *.c -exec grep -q -s Chinput {} \;-print -------------------------------------------------------------------------------- Linux必学的60个命令(3)-系统管理命令 Linux必学的系统管理命令 对于Linux系统来说,无论是中央处理器、内存、磁盘驱动器、键盘、鼠标,还是用户等都是文件,Linux系统管理的命令是它正常运行的核心。熟悉了Linux常用的文件处理命令以后,这一讲介绍对系统和用户进行管理的命令。 df 1.作用 df命令用来检查文件系统的磁盘空间占用情况,使用权限是所有用户。 2.格式 df [options] 3.主要参数 -s:对每个Names参数只给出占用的数据块总数。 -a:递归地显示指定目录中各文件及子目录中各文件占用的数据块数。若既不指定-s,也不指定-a,则只显示Names中的每一个目录及其中的各子目录所占的磁盘块数。 -k:以1024字节为单位列出磁盘空间使用情况。 -x:跳过在不同文件系统上的目录不予统计。 -l:计算所有的文件大小,对硬链接文件则计算多次。 -i:显示inode信息而非块使用量。 -h:以容易理解的格式印出文件系统大小,例如136KB、254MB、21GB。 -P:使用POSIX输出格式。 -T:显示文件系统类型。 4.说明 df命令被广泛地用来生成文件系统的使用统计数据,它能显示系统中所有的文件系统的信息,包括总容量、可用的空闲空间、目前的安装点等。 超级权限用户使用df命令时会发现这样的情况:某个分区的容量超过了100%。这是因为Linux系统为超级用户保留了10%的空间,由其单独支配。也就是说,对于超级用户而言,他所见到的硬盘容量将是110%。这样的安排对于系统管理而言是有好处的,当硬盘被使用的容量接近100%时系统管理员还可以正常工作。 5.应用实例 Linux支持的文件系统非常多,包括JFS、ReiserFS、ext、ext2、ext3、ISO9660、XFS、Minx、vfat、MSDOS等。使用df -T命令查看磁盘空间时还可以得到文件系统的信息: #df -T 文件系统 类型 容量 已用 可用 已用% 挂载点 /dev/hda7 reiserfs 5.2G 1.6G 3.7G 30% / /dev/hda1 vfat 2.4G 1.6G 827M 66% /windows/C /dev/hda5 vfat 3.0G 1.7G 1.3G 57% /windows/D /dev/hda9 vfat 3.0G 2.4G 566M 82% /windows/E /dev/hda10 NTFS 3.2G 573M 2.6G 18% /windows/F /dev/hda11 vfat 1.6G 1.5G 23M 99% /windows/G 从上面除了可以看到磁盘空间的容量、使用情况外,分区的文件系统类型、挂载点等信息也一览无遗。 top 1.作用 top命令用来显示执行中的程序进程,使用权限是所有用户。 2.格式 top [-] [d delay] [q] [c] [S] [s] [i] [n] 3.主要参数 d:指定更新的间隔,以秒计算。 q:没有任何延迟的更新。如果使用者有超级用户,则top命令将会以最高的优先序执行。 c:显示进程完整的路径与名称。 S:累积模式,会将己完成或消失的子行程的CPU时间累积起来。 s:安全模式。 i:不显示任何闲置(Idle)或无用(Zombie)的行程。 n:显示更新的次数,完成后将会退出top。 4.说明 top命令是Linux系统管理的一个主要命令,通过它可以获得许多信息。这里我们结合图1来说明它给出的信息。 图1 top命令的显示 在图1中,第一行表示的项目依次为当前时间、系统启动时间、当前系统登录用户数目、平均负载。第二行显示的是所有启动的进程、目前运行的、挂起 (Sleeping)的和无用(Zombie)的进程。第三行显示的是目前CPU的使用情况,包括系统占用的比例、用户使用比例、闲置(Idle)比例。第四行显示物理内存的使用情况,包括总的可以使用的内存、已用内存、空闲内存、缓冲区占用的内存。第五行显示交换分区使用情况,包括总的交换分区、使用的、空闲的和用于高速缓存的大小。第六行显示的项目最多,下面列出了详细解释。 PID(Process ID):进程标示号。 USER:进程所有者的用户名。 PR:进程的优先级别。 NI:进程的优先级别数值。 VIRT:进程占用的虚拟内存值。 RES:进程占用的物理内存值。 SHR:进程使用的共享内存值。 S:进程的状态,其中S表示休眠,R表示正在运行,Z表示僵死状态,N表示该进程优先值是负数。 %CPU:该进程占用的CPU使用率。 %MEM:该进程占用的物理内存和总内存的百分比。 TIME+:该进程启动后占用的总的CPU时间。 Command:进程启动的启动命令名称,如果这一行显示不下,进程会有一个完整的命令行。 top命令使用过程中,还可以使用一些交互的命令来完成其它参数的功能。这些命令是通过快捷键启动的。 :立刻刷新。 P:根据CPU使用大小进行排序。 T:根据时间、累计时间排序。 q:退出top命令。 m:切换显示内存信息。 t:切换显示进程和CPU状态信息。 c:切换显示命令名称和完整命令行。 M:根据使用内存大小进行排序。 W:将当前设置写入~/.toprc文件中。这是写top配置文件的推荐方法。 可以看到,top命令是一个功能十分强大的监控系统的工具,对于系统管理员而言尤其重要。但是,它的缺点是会消耗很多系统资源。 5.应用实例 使用top命令可以监视指定用户,缺省情况是监视所有用户的进程。如果想查看指定用户的情况,在终端中按“U”键,然后输入用户名,系统就会切换为指定用户的进程运行界面,见图2所示。 图2 使用top命令监视指定用户 free 1.作用 free命令用来显示内存的使用情况,使用权限是所有用户。 2.格式 free [-b|-k|-m] [-o] [-s delay] [-t] [-V] 3.主要参数 -b -k -m:分别以字节(KB、MB)为单位显示内存使用情况。 -s delay:显示每隔多少秒数来显示一次内存使用情况。 -t:显示内存总和列。 -o:不显示缓冲区调节列。 4.应用实例 free命令是用来查看内存使用情况的主要命令。和top命令相比,它的优点是使用简单,并且只占用很少的系统资源。通过-S参数可以使用free命令不间断地监视有多少内存在使用,这样可以把它当作一个方便实时监控器。 #free -b -s5 使用这个命令后终端会连续不断地报告内存使用情况(以字节为单位),每5秒更新一次。 quota 1.作用 quota命令用来显示磁盘使用情况和限制情况,使用权限超级用户。 2.格式 quota [-g][-u][-v][-p] 用户名 组名 3.参数 -g:显示用户所在组的磁盘使用限制。 -u:显示用户的磁盘使用限制。 -v:显示没有分配空间的文件系统的分配情况。 -p:显示简化信息。 4.应用实例 在企业应用中磁盘配额非常重要,普通用户要学会看懂自己的磁盘使用情况。要查询自己的磁盘配额可以使用下面命令(下例中用户账号是caojh): #quota caojh Disk quotas for user caojh(uid 502): Filesystem blocks quota limit grace files quota limit grace /dev/hda3 58 200000 400000 41 500 1000 以上显示ID号为502的caojh账号,文件个数设置为500~1000个,硬盘空间限制设置为200MB~400MB。一旦磁盘配额要用完时,就需要删除一些垃圾文件或向系统管理员请求追加配额。 at 1.作用 at命令用来在指定时刻执行指定的命令序列。 2.格式 at [-V] [-q x] [-f file] [-m] time 3.主要参数 -V:显示标准错误输出。 -q:许多队列输出。 -f:从文件中读取作业。 -m:执行完作业后发送电子邮件到用户。 time:设定作业执行的时间。time格式有严格的要求,由小时、分钟、日期和时间的偏移量组成,其中日期的格式为MM.DD.YY,MM是分钟,DD是日期,YY是指年份。偏移量的格式为时间+偏移量,单位是minutes、hours和days。 4.应用实例 #at -f data 15:30 +2 days 上面命令表示让系统在两天后的17:30执行文件data中指明的作业。 lp 1.作用 lp是打印文件的命令,使用权限是所有用户。 2.格式 lp [-c][-d][-m][-number][-title][-p] 3.主要参数 -c:先拷贝文件再打印。 -d:打印队列文件。 -m:打印结束后发送电子邮件到用户。 -number:打印份数。 -title:打印标题。 -p:设定打印的优先级别,最高为100。 4.应用实例 (1)使用lp命令打印多个文件 #lp 2 3 4 request id is 11 (3 file(s)) 其中2、3、4分别是文件名;“request id is 11 (3 file(s)) ”表示这是第11个打印命令,依次打印这三个文件。 (2)设定打印优先级别 #lp lp -d LaserJet -p 90 /etc/aliases 通过添加“-p 90”,规定了打印作业的优先级为90。它将在优先级低于90的打印作业之前打印,包括没有设置优先级的作业,缺省优先级是50 useradd 1.作用 useradd命令用来建立用户帐号和创建用户的起始目录,使用权限是超级用户。 2.格式 useradd [-d home] [-s shell] [-c comment] [-m [-k template]] [-f inactive] [-e expire ] [-p passwd] [-r] name 3.主要参数 -c:加上备注文字,备注文字保存在passwd的备注栏中。  -d:指定用户登入时的启始目录。 -D:变更预设值。 -e:指定账号的有效期限,缺省表示永久有效。 -f:指定在密码过期后多少天即关闭该账号。 -g:指定用户所属的群组。 -G:指定用户所属的附加群组。 -m:自动建立用户的登入目录。 -M:不要自动建立用户的登入目录。 -n:取消建立以用户名称为名的群组。 -r:建立系统账号。 -s:指定用户登入后所使用的shell。 -u:指定用户ID号。 4.说明 useradd可用来建立用户账号,它和adduser命令是相同的。账号建好之后,再用passwd设定账号的密码。使用useradd命令所建立的账号,实际上是保存在/etc/passwd文本文件中。 5.应用实例 建立一个新用户账户,并设置ID: #useradd caojh -u 544 需要说明的是,设定ID值时尽量要大于500,以免冲突。因为Linux安装后会建立一些特殊用户,一般0到499之间的值留给bin、mail这样的系统账号。 groupadd 1.作用 groupadd命令用于将新组加入系统。 2.格式 groupadd [-g gid] [-o]] [-r] [-f] groupname 3.主要参数 -g gid:指定组ID号。 -o:允许组ID号,不必惟一。 -r:加入组ID号,低于499系统账号。 -f:加入已经有的组时,发展程序退出。 4.应用实例 建立一个新组,并设置组ID加入系统: #groupadd -g 344 cjh 此时在/etc/passwd文件中产生一个组ID(GID)是344的项目。 kill 1.作用 kill命令用来中止一个进程。 2.格式 kill [ -s signal | -p ] [ -a ] pid ... kill -l [ signal ] 3.参数 -s:指定发送的信号。 -p:模拟发送信号。 -l:指定信号的名称列表。 pid:要中止进程的ID号。 Signal:表示信号。 4.说明 进程是Linux系统中一个非常重要的概念。Linux是一个多任务的操作系统,系统上经常同时运行着多个进程。我们不关心这些进程究竟是如何分配的,或者是内核如何管理分配时间片的,所关心的是如何去控制这些进程,让它们能够很好地为用户服务。 Linux 操作系统包括三种不同类型的进程,每种进程都有自己的特点和属性。交互进程是由一个Shell启动的进程。交互进程既可以在前台运行,也可以在后台运行。批处理进程和终端没有联系,是一个程序列。监控进程(也称系统守护进程)时Linux系统启动时启动的进程,并在后台运行。例如,httpd是著名的 Apache服务器的监控进程。 kill命令的工作原理是,向Linux系统的内核发送一个系统操作信号和某个程序的进程标识号,然后系统内核就可以对进程标识号指定的进程进行操作。比如在top命令中,我们看到系统运行许多进程,有时就需要使用kill中止某些进程来提高系统资源。在讲解安装和登陆命令时,曾提到系统多个虚拟控制台的作用是当一个程序出错造成系统死锁时,可以切换到其它虚拟控制台工作关闭这个程序。此时使用的命令就是kill,因为kill是大多数Shell内部命令可以直接调用的。 5.应用实例 (1)强行中止(经常使用杀掉)一个进程标识号为324的进程: #kill -9 324 (2)解除Linux系统的死锁 在Linux 中有时会发生这样一种情况:一个程序崩溃,并且处于死锁的状态。此时一般不用重新启动计算机,只需要中止(或者说是关闭)这个有问题的程序即可。当 kill处于X-Window界面时,主要的程序(除了崩溃的程序之外)一般都已经正常启动了。此时打开一个终端,在那里中止有问题的程序。比如,如果 Mozilla浏览器程序出现了锁死的情况,可以使用kill命令来中止所有包含有Mozolla浏览器的程序。首先用top命令查处该程序的PID,然后使用kill命令停止这个程序: #kill -SIGKILL XXX 其中,XXX是包含有Mozolla浏览器的程序的进程标识号。 (3)使用命令回收内存 我们知道内存对于系统是非常重要的,回收内存可以提高系统资源。kill命令可以及时地中止一些“越轨”的程序或很长时间没有相应的程序。例如,使用top命令发现一个无用 (Zombie) 的进程,此时可以使用下面命令: #kill -9 XXX 其中,XXX是无用的进程标识号。 然后使用下面命令: #free 此时会发现可用内存容量增加了。 (4)killall命令 Linux下还提供了一个killall命令,可以直接使用进程的名字而不是进程标识号,例如: # killall -HUP inetd crontab 1.作用 使用crontab命令可以修改crontab配置文件,然后该配置由cron公用程序在适当的时间执行,该命令使用权限是所有用户。 2.格式 crontab [ -u user ] 文件 crontab [ -u user ] { -l | -r | -e } 3.主要参数 -e:执行文字编辑器来设定时程表,内定的文字编辑器是vi。 -r:删除目前的时程表。 -l:列出目前的时程表。 crontab 文件的格式为“M H D m d cmd”。其中,M代表分钟(0~59),H代表小时(0~23),D代表天(1~31),m代表月(1~12),d代表一星期内的天(0~6,0为星期天)。cmd表示要运行的程序,它被送入sh执行,这个Shell只有USER、HOME、SHELL三个环境变量。 4.说明 和at命令相比,crontab命令适合完成固定周期的任务。 5.应用实例 设置一个定时、定期的系统提示: [cao @www cao]#crontab -e 此时系统会打开一个vi编辑器。 如果输入以下内容:35 17 * * 5 wall "Tomorrow is Saturday I will go CS",然后存盘退出。这时在/var/spool/cron/目录下会生产一个cao的文件,内容如下: # DO NOT EDIT THIS FILE - edit the master and reinstall. # (/tmp/crontab.2707 installed on Thu Jan 1 22:01:51 2004) # (Cron version -- $Id: crontab.c,v 2.13 1994/01/17 03:20:37 vixie Exp $) 35 17 * * 5 wall "Tomorrow is Saturday I will play CS " 这样每个星期五17:35系统就会弹出一个终端,提醒星期六可以打打CS了!显示结果见图3所示。 图3 一个定时、定期的系统提示 动手练习 1.联合使用kill和top命令观察系统性能的变化 首先启动一个终端运行top命令,然后再启动一个终端使用kill命令,见图4所示。 图4 观察kill命令对top终端的影响 这时利用上面介绍的kill命令来中止一些程序: #kill SIGKILL XXX 然后再看top命令终端的变化,包括内存容量、CPU使用率、系统负载等。注意,有些进程是不能中止的,不过学习Linux命令时可以试试,看看系统有什么反应。 2.使用at和halt命令定时关机 首先设定关机时间是17:35,输入下面代码: #at 17:35 warning: commands will be executed using (in order) a) $SHELL b) login shell c) /bin/sh at>halt `-i -p at> job 6 at 2004-01-01 17:35 此时实际上就已经进入Linux系统的Shell,并且编写一个最简单程序:halt -i -p。上面Shell中的文本结束符号表示按“Ctrl+D”组合键关闭命令,提交任务退出Shell。“Job 6 at 2004-01-01 17:35”表示系统接受第6个at命令,在“2004-01-01 17:35”时执行命令:先把所有网络相关的装置停止,关闭系统后关闭电源。 3.用crontab命令实现每天定时的病毒扫描 前面已经介绍了一个简单的crontab命令操作,这里看一些更重要的操作。 (1)建立一个文件,文件名称自己设定,假设为caoproject: #crontab -e (2)文件内容如下: 05 09 * * * antivir 用vi编辑后存盘退出。antivir是一个查杀Linux病毒的软件,当然需要时先安装在系统中。 (3)使用crontab命令添加到任务列表中: #crontab caoproject 这样系统内所有用户在每天的9点05分会自动进行病毒扫描。 4.用kill使修改的配置文件马上生效 Windows用户一般都知道,重要配置文件修改后往往都要重新启动计算机才能使修改生效。而Linux由于采用了模块化设计,可以自己根据需要实时设定服务。这里以网络服务inetd为例介绍一些操作技巧。 inetd 是一个监听守护进程,监听与提供互联网服务进程(如rlogin、telnet、ftp、rsh)进行连接的要求,并扩展所需的服务进程。默认情况下, inetd监听的这些daemon均列于/etc /inetd.conf文件中。编辑/etc/inetd.conf文件,可以改变inetd启动服务器守护进程的选项,然后驱使inetd以 SIGHUP(signal 1)向当前的inetd进程发送信号,使inetd重读该文件。这一过程由kill命令来实现。 用vi或其它编辑器修改inetd.conf后,首先使用下面命令: #ps -ef |grep inetd 上面代码表明查询inetd.conf的进程号(PID),这里假设是1426,然后使用下面命令: # kill -1426 inetd 这样配置文件就生效了。 这一讲介绍的系统管理命令都是比较重要的,特别是crontab命令和quota命令使用起来会有一定难度,需要多做一些练习。另外,使用kill命令要注意“-9“这个参数,练习时最好不要运行一些重要的程序。 -------------------------------------------------------------------------------- Linux必学的60个命令(4)-网络操作命令 Linux必学的60个命令:网络操作命令 因为Linux系统是在Internet上起源和发展的,它与生俱来拥有强大的网络功能和丰富的网络应用软件,尤其是TCP/IP网络协议的实现尤为成熟。 Linux的网络命令比较多,其中一些命令像ping、 ftp、telnet、route、netstat等在其它操作系统上也能看到,但也有一些Unix/Linux系统独有的命令,如ifconfig、 finger、mail等。Linux网络操作命令的一个特点是,命令参数选项和功能很多,一个命令往往还可以实现其它命令的功能。 ifconfig 1.作用 ifconfig用于查看和更改网络接口的地址和参数,包括IP地址、网络掩码、广播地址,使用权限是超级用户。 2.格式 ifconfig -interface [options] address 3.主要参数 -interface:指定的网络接口名,如eth0和eth1。 up:激活指定的网络接口卡。 down:关闭指定的网络接口。 broadcast address:设置接口的广播地址。 pointopoint:启用点对点方式。 address:设置指定接口设备的IP地址。 netmask address:设置接口的子网掩码。 4.应用说明 ifconfig是用来设置和配置网卡的命令行工具。为了手工配置网络,这是一个必须掌握的命令。使用该命令的好处是无须重新启动机器。要赋给eth0接口IP地址207.164.186.2,并且马上激活它,使用下面命令: #fconfig eth0 210.34.6.89 netmask 255.255.255.128 broadcast 210.34.6.127 该命令的作用是设置网卡eth0的IP地址、网络掩码和网络的本地广播地址。若运行不带任何参数的ifconfig命令,这个命令将显示机器所有激活接口的信息。带有“-a”参数的命令则显示所有接口的信息,包括没有激活的接口。注意,用ifconfig命令配置的网络设备参数,机器重新启动以后将会丢失。 如果要暂停某个网络接口的工作,可以使用down参数: #ifconfig eth0 down ip 1.作用 ip是iproute2软件包里面的一个强大的网络配置工具,它能够替代一些传统的网络管理工具,例如ifconfig、route等,使用权限为超级用户。几乎所有的Linux发行版本都支持该命令。 2.格式 ip [OPTIONS] OBJECT [COMMAND [ARGUMENTS]] 3.主要参数 OPTIONS是修改ip行为或改变其输出的选项。所有的选项都是以-字符开头,分为长、短两种形式。目前,ip支持如表1所示选项。 OBJECT是要管理者获取信息的对象。目前ip认识的对象见表2所示。 表1 ip支持的选项 -V,-Version 打印ip的版本并退出。 -s,-stats,-statistics 输出更为详尽的信息。如果这个选项出现两次或多次,则输出的信息将更为详尽。 -f,-family 这个选项后面接协议种类,包括inet、inet6或link,强调使用的协议种类。如果没有足够的信息告诉ip使用的协议种类,ip就会使用默认值inet或any。link比较特殊,它表示不涉及任何网络协议。 -4 是-family inet的简写。 -6 是-family inet6的简写。 -0 是-family link的简写。 -o,-oneline 对每行记录都使用单行输出,回行用字符代替。如果需要使用wc、grep等工具处理ip的输出,则会用到这个选项。 -r,-resolve 查询域名解析系统,用获得的主机名代替主机IP地址 COMMAND 设置针对指定对象执行的操作,它和对象的类型有关。一般情况下,ip支持对象的增加(add)、删除(delete)和展示(show或list)。有些对象不支持这些操作,或者有其它的一些命令。对于所有的对象,用户可以使用help命令获得帮助。这个命令会列出这个对象支持的命令和参数的语法。如果没有指定对象的操作命令,ip会使用默认的命令。一般情况下,默认命令是list,如果对象不能列出,就会执行help命令。 ARGUMENTS 是命令的一些参数,它们倚赖于对象和命令。ip支持两种类型的参数:flag和parameter。flag由一个关键词组成;parameter由一个关键词加一个数值组成。为了方便,每个命令都有一个可以忽略的默认参数。例如,参数dev是ip link命令的默认参数,因此ip link ls eth0等于ip link ls dev eth0。我们将在后面的详细介绍每个命令的使用,命令的默认参数将使用default标出。 4.应用实例 添加IP地址192.168.2.2/24到eth0网卡上: #ip addr add 192.168.1.1/24 dev eth0 丢弃源地址属于192.168.2.0/24网络的所有数据报: #ip rule add from 192.168.2.0/24 prio 32777 reject ping 1.作用 ping检测主机网络接口状态,使用权限是所有用户。 2.格式 ping [-dfnqrRv][-c][-i][-I][-l][-p][-s][-t] IP地址 3.主要参数 -d:使用Socket的SO_DEBUG功能。 -c:设置完成要求回应的次数。 -f:极限检测。 -i:指定收发信息的间隔秒数。 -I:网络界面使用指定的网络界面送出数据包。 -l:前置载入,设置在送出要求信息之前,先行发出的数据包。 -n:只输出数值。 -p:设置填满数据包的范本样式。 -q:不显示指令执行过程,开头和结尾的相关信息除外。 -r:忽略普通的Routing Table,直接将数据包送到远端主机上。 -R:记录路由过程。 -s:设置数据包的大小。 -t:设置存活数值TTL的大小。 -v:详细显示指令的执行过程。 ping 命令是使用最多的网络指令,通常我们使用它检测网络是否连通,它使用ICMP协议。但是有时会有这样的情况,我们可以浏览器查看一个网页,但是却无法 ping通,这是因为一些网站处于安全考虑安装了防火墙。另外,也可以在自己计算机上试一试,通过下面的方法使系统对ping没有反应: # echo 1 > /proc/sys/net/ipv4/icmp_echo_ignore_all netstat 1.作用 检查整个Linux网络状态。 2.格式 netstat [-acCeFghilMnNoprstuvVwx][-A][--ip] 3.主要参数 -a--all:显示所有连线中的Socket。 -A:列出该网络类型连线中的IP相关地址和网络类型。 -c--continuous:持续列出网络状态。 -C--cache:显示路由器配置的快取信息。 -e--extend:显示网络其它相关信息。 -F--fib:显示FIB。 -g--groups:显示多重广播功能群组组员名单。 -h--help:在线帮助。 -i--interfaces:显示网络界面信息表单。 -l--listening:显示监控中的服务器的Socket。 -M--masquerade:显示伪装的网络连线。 -n--numeric:直接使用IP地址,而不通过域名服务器。 -N--netlink--symbolic:显示网络硬件外围设备的符号连接名称。 -o--timers:显示计时器。 -p--programs:显示正在使用Socket的程序识别码和程序名称。 -r--route:显示Routing Table。 -s--statistice:显示网络工作信息统计表。 -t--tcp:显示TCP传输协议的连线状况。 -u--udp:显示UDP传输协议的连线状况。 -v--verbose:显示指令执行过程。 -V--version:显示版本信息。 -w--raw:显示RAW传输协议的连线状况。 -x--unix:和指定“-A unix”参数相同。 --ip--inet:和指定“-A inet”参数相同。 4.应用实例 netstat 主要用于Linux察看自身的网络状况,如开启的端口、在为哪些用户服务,以及服务的状态等。此外,它还显示系统路由表、网络接口状态等。可以说,它是一个综合性的网络状态的察看工具。在默认情况下,netstat只显示已建立连接的端口。如果要显示处于监听状态的所有端口,使用-a参数即可: #netstat -a Active Internet connections (only servers) Proto Recv-Q Send-Q Local Address Foreign Address State tcp 0 0 *:32768 *:* LISTEN tcp 0 0 *:32769 *:* LISTEN tcp 0 0 *:nfs *:* LISTEN tcp 0 0 *:32770 *:* LISTEN tcp 0 0 *:868 *:* LISTEN tcp 0 0 *:617 *:* LISTEN tcp 0 0 *:mysql *:* LISTEN tcp 0 0 *:netbios-ssn *:* LISTEN tcp 0 0 *:sunrpc *:* LISTEN tcp 0 0 *:10000 *:* LISTEN tcp 0 0 *:http *:* LISTEN ...... 上面显示出,这台主机同时提供HTTP、FTP、NFS、MySQL等服务。 telnet 1.作用 telnet表示开启终端机阶段作业,并登入远端主机。telnet是一个Linux命令,同时也是一个协议(远程登陆协议)。 2.格式 telnet [-8acdEfFKLrx][-b][-e][-k][-l][-n][-S][-X][主机名称IP地址] 3.主要参数 -8:允许使用8位字符资料,包括输入与输出。 -a:尝试自动登入远端系统。 -b:使用别名指定远端主机名称。 -c:不读取用户专属目录里的.telnetrc文件。 -d:启动排错模式。 -e:设置脱离字符。 -E:滤除脱离字符。 -f:此参数的效果和指定“-F”参数相同。 -F:使用Kerberos V5认证时,加上此参数可把本地主机的认证数据上传到远端主机。 -k:使用Kerberos认证时,加上此参数让远端主机采用指定的领域名,而非该主机的域名。 -K:不自动登入远端主机。 -l:指定要登入远端主机的用户名称。 -L:允许输出8位字符资料。 -n:指定文件记录相关信息。 -r:使用类似rlogin指令的用户界面。 -S:服务类型,设置telnet连线所需的IP TOS信息。 -x:假设主机有支持数据加密的功能,就使用它。 -X:关闭指定的认证形态。 4.应用说明 用户使用telnet命令可以进行远程登录,并在远程计算机之间进行通信。用户通过网络在远程计算机上登录,就像登录到本地机上执行命令一样。为了通过 telnet登录到远程计算机上,必须知道远程机上的合法用户名和口令。虽然有些系统确实为远程用户提供登录功能,但出于对安全的考虑,要限制来宾的操作权限,因此,这种情况下能使用的功能是很少的。 t
Visual C++MFC入门教程 目录 +-- 第一章 VC入门 |------ 1.1 如何学好VC |------ 1.2 理解Windows消息机制 |------ 1.3 利用Visual C++/MFC开发Windows程序的优势 |------ 1.4 利用MFC进行开发的通用方法介绍 |------ 1.5 MFC中常用类,宏,函数介绍 +-- 第二章 图形输出 |------ 2.1 和GUI有关的各种对象 |------ 2.2 在窗口中输出文字 |------ 2.3 使用点,刷子,笔进行绘图 |------ 2.4 在窗口中绘制设备相关位图,图标,设备无关位图 |------ 2.5 使用各种映射方式 |------ 2.6 多边形和剪贴区域 +-- 第三章 文档视结构 |------ 3.1 文档 视图 框架窗口间的关系和消息传送规律 |------ 3.2 接收用户输入 |------ 3.3 使用菜单 |------ 3.4 文档,视,框架之间相互作用 |------ 3.5 利用序列化进行文件读写 |------ 3.6 MFC中所提供的各种视类介绍 +-- 第四章 窗口控件 |------ 4.1 Button |------ 4.2 Static Box |------ 4.3 Edit Box |------ 4.4 Scroll Bar |------ 4.5 List Box/Check List Box |------ 4.6 Combo Box/Combo Box Ex |------ 4.7 Tree Ctrl |------ 4.8 List Ctrl |------ 4.9 Tab Ctrl |------ 4.A Tool Bar |------ 4.B Status Bar |------ 4.C Dialog Bar |------ 4.D 利用AppWizard创建并使用ToolBar StatusBar Dialog Bar |------ 4.E General Window |------ 4.F 关于WM_NOTIFY的使用方法 +-- 第五章 对话框 |------ 5.1 使用资源编辑器编辑对话框 |------ 5.2 创建有模式对话框 |------ 5.3 创建无模式对话框 |------ 5.4 在对话框中进行消息映射 |------ 5.5 在对话框中进行数据交换和数据检查 |------ 5.6 使用属性对话框 |------ 5.7 使用通用对话框 |------ 5.8 建立以对话框为基础的应用 |------ 5.9 使用对话框作为子窗口 +-- 第六章 网络通信开发 |------ 6.1 WinSock介绍 |------ 6.2 利用WinSock进行无连接的通信 +------ 6.3 利用WinSock建立有连接的通信   第一章 VC入门 1.1 如何学好VC 这个问题很多朋友都问过我,当然流汗是必须的,但同时如果按照某种思路进行有计划的学习就会起到更好的效果。万事开头难,为了帮助朋友们更快的掌握VC开发,下面我将自己的一点体会讲一下: 1、需要有好的C/C++基础。正所谓“磨刀不误砍柴工”,最开始接触VC时不要急于开始Windows程序开发,而是应该进行一些字符界面程序编写。这样做的目的主要是增加对语言的熟悉程度,同时也训练自己的思维和熟悉一些在编程中常犯的错误。更重要的是理解并能运用C++的各种特性,这些在以后的开发中都会有很大的帮助,特别是利用MFC进行开发的朋友对C++一定要能熟练运用。 2、理解Windows的消息机制,窗口句柄和其他GUI句柄的含义和用途。了解和MFC各个类功能相近的API函数。 3、一定要理解MFC中消息映射的作用。 4、训练自己在编写代码时不使用参考书而是使用Help Online。 5、记住一些常用的消息名称和参数的意义。 6、学会看别人的代码。 7、多看书,少买书,买书前一定要慎重。 8、闲下来的时候就看参考书。 9、多来我的主页。^O^ 后面几条是我个人的一点意见,你可以根据需要和自身的情况选用适用于自己的方法。 此外我将一些我在选择参考书时的原则: 对于初学者:应该选择一些内容比较全面的书籍,并且书籍中的内容应该以合理的方式安排,在使用该书时可以达到循序渐进的效果,书中的代码要有详细的讲解。尽量买翻译的书,因为这些书一般都比较易懂,而且语言比较轻松。买书前一定要慎重如果买到不好用的书可能会对自己的学习积极性产生击。 对于已经掌握了VC的朋友:这种程度的开发者应该加深自己对系统原理,技术要点的认识。需要选择一些对原理讲解的比较透彻的书籍,这样一来才会对新技术有更多的了解,最好书中对技术的应用有一定的阐述。尽量选择示范代码必较精简的书,可以节约银子。 此外最好涉猎一些辅助性的书籍。 1.2 理解Windows消息机制 Windows系统是一个消息驱动的OS,什么是消息呢?我很难说得清楚,也很难下一个定义(谁在嘘我),我下面从不同的几个方面讲解一下,希望大家看了后有一点了解。 1、消息的组成:一个消息由一个消息名称(UINT),和两个参数(WPARAM,LPARAM)。当用户进行了输入或是窗口的状态发生改变时系统都会发送消息到某一个窗口。例如当菜单转中之后会有WM_COMMAND消息发送,WPARAM的高字中(HIWORD(wParam))是命令的ID号,对菜单来讲就是菜单ID。当然用户也可以定义自己的消息名称,也可以利用自定义消息来发送通知和传送数据。 2、谁将收到消息:一个消息必须由一个窗口接收。在窗口的过程(WNDPROC)中可以对消息进行分析,对自己感兴趣的消息进行处理。例如你希望对菜单选择进行处理那么你可以定义对WM_COMMAND进行处理的代码,如果希望在窗口中进行图形输出就必须对WM_PAINT进行处理。 3、未处理的消息到那里去了:M$为窗口编写了默认的窗口过程,这个窗口过程将负责处理那些你不处理消息。正因为有了这个默认窗口过程我们才可以利用Windows的窗口进行开发而不必过多关注窗口各种消息的处理。例如窗口在被拖动时会有很多消息发送,而我们都可以不予理睬让系统自己去处理。 4、窗口句柄:说到消息就不能不说窗口句柄,系统通过窗口句柄来在整个系统中唯一标识一个窗口,发送一个消息时必须指定一个窗口句柄表明该消息由那个窗口接收。而每个窗口都会有自己的窗口过程,所以用户的输入就会被正确的处理。例如有两个窗口共用一个窗口过程代码,你在窗口一上按下鼠标时消息就会通过窗口一的句柄被发送到窗口一而不是窗口二。 5、示例:下面有一段伪代码演示如何在窗口过程中处理消息 LONG yourWndProc(HWND hWnd,UINT uMessageType,WPARAM wP,LPARAM) { switch(uMessageType) { //使用SWITCH语句将各种消息分开 case(WM_PAINT): doYourWindow(...);//在窗口需要重新绘制时进行输出 break; case(WM_LBUTTONDOWN): doYourWork(...);//在鼠标左键被按下时进行处理 break; default: callDefaultWndProc(...);//对于其它情况就让系统自己处理 break; } } 接下来谈谈什么是消息机制:系统将会维护一个或多个消息队列,所有产生的消息都回被放入或是插入队列中。系统会在队列中取出每一条消息,根据消息的接收句柄而将该消息发送给拥有该窗口的程序的消息循环。每一个运行的程序都有自己的消息循环,在循环中得到属于自己的消息并根据接收窗口的句柄调用相应的窗口过程。而在没有消息时消息循环就将控制权交给系统所以Windows可以同时进行多个任务。下面的伪代码演示了消息循环的用法: while(1) { id=getMessage(...); if(id == quit) break; translateMessage(...); } 当该程序没有消息通知时getMessage就不会返回,也就不会占用系统的CPU时间。 下图为消息投递模式 在16位的系统中系统中只有一个消息队列,所以系统必须等待当前任务处理消息后才可以发送下一消息到相应程序,如果一个程序陷如死循环或是耗时操作时系统就会得不到控制权。这种多任务系统也就称为协同式的多任务系统。Windows3.X就是这种系统。而32位的系统中每一运行的程序都会有一个消息队列,所以系统可以在多个消息队列中转换而不必等待当前程序完成消息处理就可以得到控制权。这种多任务系统就称为抢先式的多任务系统。Windows95/NT就是这种系统。 1.3 利用Visual C++/MFC开发Windows程序的优势 MFC借助C++的优势为Windows开发开辟了一片新天地,同时也借助ApplicationWizzard使开发者摆脱离了那些每次都必写基本代码,借助ClassWizard和消息映射使开发者摆脱了定义消息处理时那种混乱和冗长的代码段。更令人兴奋的是利用C++的封装功能使开发者摆脱Windows中各种句柄的困扰,只需要面对C++中的对象,这样一来使开发更接近开发语言而远离系统。(但我个人认为了解系统原理对开发很有帮助) 正因为MFC是建立在C++的基础上,所以我强调C/C++语言基础对开发的重要性。利用C++的封装性开发者可以更容易理解和操作各种窗口对象;利用C++的派生性开发者可以减少开发自定义窗口的时间和创造出可重用的代码;利用虚拟性可以在必要时更好的控制窗口的活动。而且C++本身所具备的超越C语言的特性都可以使开发者编写出更易用,更灵活的代码。 在MFC中对消息的处理利用了消息映射的方法,该方法的基础是宏定义实现,通过宏定义将消息分派到不同的成员函数进行处理。下面简单讲述一下这种方法的实现方法: 代码如下 BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd) //{{AFX_MSG_MAP(CMainFrame) ON_WM_CREATE() //}}AFX_MSG_MAP ON_COMMAND(ID_FONT_DROPDOWN, DoNothing) END_MESSAGE_MAP() 经过编译后,代码被替换为如下形式(这只是作讲解,实际情况比这复杂得多): //BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd) CMainFrame::newWndProc(...) { switch(...) { //{{AFX_MSG_MAP(CMainFrame) // ON_WM_CREATE() case(WM_CREATE): OnCreate(...); break; //}}AFX_MSG_MAP // ON_COMMAND(ID_FONT_DROPDOWN, DoNothing) case(WM_COMMAND): if(HIWORD(wP)==ID_FONT_DROPDOWN) { DoNothing(...); } break; //END_MESSAGE_MAP() } } newWndProc就是窗口过程只要是该类的实例生成的窗口都使用该窗口过程。 所以了解了Windows的消息机制在加上对消息映射的理解就很容易了解MFC开发的基本思路了。 1.4 利用MFC进行开发的通用方法介绍 以下是我在最初学习VC时所常用的开发思路和方法,希望能对初学VC的朋友有所帮助和启发。 1、开发需要读写文件的应用程序并且有简单的输入和输出可以利用单文档视结构。 2、开发注重交互的简单应用程序可以使用对话框为基础的窗口,如果文件读写简单这可利用CFile进行。 3、开发注重交互并且文件读写复杂的的简单应用程序可以利用以CFormView为基础视的单文档视结构。 4、利用对话框得到用户输入的数据,在等级提高后可使用就地输入。 5、在对多文档要求不强烈时尽量避免多文档视结构,可以利用分隔条产生单文档多视结构。 6、在要求在多个文档间传递数据时使用多文档视结构。 7、学会利用子窗口,并在自定义的子窗口包含多个控件达到封装功能的目的。 8、尽量避免使用多文档多视结构。 9、不要使用多重继承并尽量减少一个类中封装过多的功能。 1.5 MFC中常用类,宏,函数介绍 常用类 CRect:用来表示矩形的类,拥有四个成员变量:top left bottom right。分别表是左上角和右下角的坐标。可以通过以下的方法构造: CRect( int l, int t, int r, int b ); 指明四个坐标 CRect( const RECT& srcRect ); 由RECT结构构造 CRect( LPCRECT lpSrcRect ); 由RECT结构构造 CRect( POINT point, SIZE size ); 有左上角坐标和尺寸构造 CRect( POINT topLeft, POINT bottomRight ); 有两点坐标构造 下面介绍几个成员函数: int Width( ) const; 得到宽度 int Height( ) const; 得到高度 CSize Size( ) const; 得到尺寸 CPoint& TopLeft( ); 得到左上角坐标 CPoint& BottomRight( ); 得到右下角坐标 CPoint CenterPoint( ) const; 得当中心坐标 此外矩形可以和点(CPoint)相加进行位移,和另一个矩形相加得到“并”操作后的矩形。 CPoint:用来表示一个点的坐标,有两个成员变量:x y。 可以和另一个点相加。 CString:用来表示可变长度的字符串。使用CString可不指明内存大小,CString会根据需要自行分配。下面介绍几个成员函数: GetLength 得到字符串长度 GetAt 得到指定位置处的字符 operator + 相当于strcat void Format( LPCTSTR lpszFormat, ... ); 相当于sprintf Find 查找指定字符,字符串 Compare 比较 CompareNoCase 不区分大小写比较 MakeUpper 改为小写 MakeLower 改为大写 CStringArray:用来表示可变长度的字符串数组。数组中每一个元素为CString对象的实例。下面介绍几个成员函数: Add 增加CString RemoveAt 删除指定位置CString对象 RemoveAll 删除数组中所有CString对象 GetAt 得到指定位置的CString对象 SetAt 修改指定位置的CString对象 InsertAt 在某一位置插入CString对象 常用宏 RGB TRACE ASSERT VERIFY 常用函数 CWindApp* AfxGetApp(); HINSTANCE AfxGetInstanceHandle( ); HINSTANCE AfxGetResourceHandle( ); int AfxMessageBox( LPCTSTR lpszText, UINT nType = MB_OK, UINT nIDHelp = 0 );用于弹出一个消息框 第二章 图形输出 2.1 和GUI有关的各种对象 在Windows中有各种GUI对象(不要和C++对象混淆),当你在进行绘图就需要利用这些对象。而各种对象都拥有各种属性,下面分别讲述各种GUI对象和拥有的属性。 字体对象CFont用于输出文字时选用不同风格和大小的字体。可选择的风格包括:是否为斜体,是否为粗体,字体名称,是否有下划线等。颜色和背景色不属于字体的属性。关于如何创建和使用字体在2.2 在窗口中输出文字中会详细讲解。 刷子CBrush对象决定填充区域时所采用的颜色或模板。对于一个固定色的刷子来讲它的属性为颜色,是否采用网格和网格的类型如水平的,垂直的,交叉的等。你也可以利用8*8的位图来创建一个自定义模板的刷子,在使用这种刷子填充时系统会利用位图逐步填充区域。关于如何创建和使用刷子在2.3 使用刷子,笔进行绘图中会详细讲解。 画笔CPen对象在画点和画线时有用。它的属性包括颜色,宽度,线的风格,如虚线,实线,点划线等。关于如何创建和使用画笔在2.3 使用刷子,笔进行绘图中会详细讲解。 位图CBitmap对象可以包含一幅图像,可以保存在资源中。关于如何使用位图在2.4 在窗口中绘制设备相关位图,图标,设备无关位图中会详细讲解。 还有一种特殊的GUI对象是多边形,利用多边形可以很好的限制作图区域或是改变窗口外型。关于如何创建和使用多边形在2.6 多边形和剪贴区域中会详细讲解。 在Windows中使用GUI对象必须遵守一定的规则。首先需要创建一个合法的对象,不同的对象创建方法不同。然后需要将该GUI对象选入DC中,同时保存DC中原来的GUI对象。如果选入一个非法的对象将会引起异常。在使用完后应该恢复原来的对象,这一点特别重要,如果保存一个临时对象在DC中,而在临时对象被销毁后可能引起异常。有一点必须注意,每一个对象在重新创建前必须销毁,下面的代码演示了这一种安全的使用方法: OnDraw(CDC* pDC) { CPen pen1,pen2; pen1.CreatePen(PS_SOLID,2,RGB(128,128,128));//创建对象 pen2.CreatePen(PS_SOLID,2,RGB(128,128,0));//创建对象 CPen* pPenOld=(CPen*)pDC->SelectObject(&pen1);//选择对象进DC drawWithPen1... (CPen*)pDC->SelectObject(&pen2);//选择对象进DC drawWithPen2... pen1.DeleteObject();//再次创建前先销毁 pen1.CreatePen(PS_SOLID,2,RGB(0,0,0));//再次创建对象 (CPen*)pDC->SelectObject(&pen1);//选择对象进DC drawWithPen1... pDC->SelectObject(pOldPen);//恢复 } 此外系统中还拥有一些库存GUI对象,你可以利用CDC::SelectStockObject(SelectStockObject( int nIndex )选入这些对象,它们包括一些固定颜色的刷子,画笔和一些基本字体。 • BLACK_BRUSH Black brush. • DKGRAY_BRUSH Dark gray brush. • GRAY_BRUSH Gray brush. • HOLLOW_BRUSH Hollow brush. • LTGRAY_BRUSH Light gray brush. • NULL_BRUSH Null brush. • WHITE_BRUSH White brush. • BLACK_PEN Black pen. • NULL_PEN Null pen. • WHITE_PEN White pen. • ANSI_FIXED_FONT ANSI fixed system font. • ANSI_VAR_FONT ANSI variable system font. • DEVICE_DEFAULT_FONT Device-dependent font. • OEM_FIXED_FONT OEM-dependent fixed font. • SYSTEM_FONT The system font. By default, Windows uses the system font to draw menus, dialog-box controls, and other text. In Windows versions 3.0 and later, the system font is proportional width; earlier versions of Windows use a fixed-width system font. • SYSTEM_FIXED_FONT The fixed-width system font used in Windows prior to version 3.0. This object is available for compatibility with earlier versions of Windows. • DEFAULT_PALETTE Default color palette. This palette consists of the 20 static colors in the system palette. 这些对象留在DC中是安全的,所以你可以利用选入库存对象来作为恢复DC中GUI对象。 大家可能都注意到了绘图时都需要一个DC对象,DC(Device Context设备环境)对象是一个抽象的作图环境,可能是对应屏幕,也可能是对应打印机或其它。这个环境是设备无关的,所以你在对不同的设备输出时只需要使用不同的设备环境就行了,而作图方式可以完全不变。这也就是Windows耀眼的一点设备无关性。如同你将对一幅画使用照相机或复印机将会产生不同的输出,而不需要对画进行任何调整。DC的使用会穿插在本章中进行介绍。 2.2 在窗口中输出文字 在这里我假定读者已经利用ApplicationWizard生成了一个SDI界面的程序代码。接下来的你只需要在CView派生类的OnDraw成员函数中加入绘图代码就可以了。在这里我需要解释一下OnDraw函数的作用,OnDraw函数会在窗口需要重绘时自动被调用,传入的参数CDC* pDC对应的就是DC环境。使用OnDraw的优点就在于在你使用打印功能的时候传入OnDraw的DC环境将会是打印机绘图环境,使用打印预览时传入的是一个称为CPreviewDC的绘图环境,所以你只需要一份代码就可以完成窗口/打印预览/打印机绘图三重功能。利用Windows的设备无关性和M$为打印预览所编写的上千行代码你可以很容易的完成一个具有所见即所得的软件。 输出文字一般使用CDC::BOOL TextOut( int x, int y, const CString& str )和CDC::int DrawText( const CString& str, LPRECT lpRect, UINT nFormat )两个函数,对TextOut来讲只能输出单行的文字,而DrawText可以指定在一个矩形中输出单行或多行文字,并且可以规定对齐方式和使用何种风格。nFormat可以是多种以下标记的组合(利用位或操作)以达到选择输出风格的目的。 • DT_BOTTOM底部对齐 Specifies bottom-justified text. This value must be combined with DT_SINGLELINE. • DT_CALCRECT计算指定文字时所需要矩形尺寸 Determines the width and height of the rectangle. If there are multiple lines of text, DrawText will use the width of the rectangle pointed to by lpRect and extend the base of the rectangle to bound the last line of text. If there is only one line of text, DrawText will modify the right side of the rectangle so that it bounds the last character in the line. In either case, DrawText returns the height of the formatted text, but does not draw the text. • DT_CENTER中部对齐 Centers text horizontally. • DT_END_ELLIPSIS or DT_PATH_ELLIPSIS Replaces part of the given string with ellipses, if necessary, so that the result fits in the specified rectangle. The given string is not modified unless the DT_MODIFYSTRING flag is specified. You can specify DT_END_ELLIPSIS to replace characters at the end of the string, or DT_PATH_ELLIPSIS to replace characters in the middle of the string. If the string contains backslash (\) characters, DT_PATH_ELLIPSIS preserves as much as possible of the text after the last backslash. • DT_EXPANDTABS Expands tab characters. The default number of characters per tab is eight. • DT_EXTERNALLEADING Includes the font抯 external leading in the line height. Normally, external leading is not included in the height of a line of text. • DT_LEFT左对齐 Aligns text flush-left. • DT_MODIFYSTRING Modifies the given string to match the displayed text. This flag has no effect unless the DT_END_ELLIPSIS or DT_PATH_ELLIPSIS flag is specified. Note Some uFormat flag combinations can cause the passed string to be modified. Using DT_MODIFYSTRING with either DT_END_ELLIPSIS or DT_PATH_ELLIPSIS may cause the string to be modified, causing an assertion in the CString override. • DT_NOCLIP Draws without clipping. DrawText is somewhat faster when DT_NOCLIP is used. • DT_NOPREFIX禁止使用&前缀 Turns off processing of prefix characters. Normally, DrawText interprets the ampersand (&) mnemonic-prefix character as a directive to underscore the character that follows, and the two-ampersand (&&) mnemonic-prefix characters as a directive to print a single ampersand. By specifying DT_NOPREFIX, this processing is turned off. • DT_PATH_ELLIPSIS • DT_RIGHT右对齐 Aligns text flush-right. • DT_SINGLELINE单行输出 Specifies single line only. Carriage returns and linefeeds do not break the line. • DT_TABSTOP设置TAB字符所占宽度 Sets tab stops. The high-order byte of nFormat is the number of characters for each tab. The default number of characters per tab is eight. • DT_TOP定部对齐 Specifies top-justified text (single line only). • DT_VCENTER中部对齐 Specifies vertically centered text (single line only). • DT_WORDBREAK每行只在单词间被折行 Specifies word-breaking. Lines are automatically broken between words if a word would extend past the edge of the rectangle specified by lpRect. A carriage return杔inefeed sequence will also break the line. 在输出文字时如果希望改变文字的颜色,你可以利用CDC::SetTextColor( COLORREF crColor )进行设置,如果你希望改变背景色就利用CDC::SetBkColor( COLORREF crColor ),很多时候你可能需要透明的背景色你可以利用CDC::SetBkMode( int nBkMode )设置,可接受的参数有 • OPAQUE Background is filled with the current background color before the text, hatched brush, or pen is drawn. This is the default background mode. • TRANSPARENT Background is not changed before drawing. 接下来讲讲如何创建字体,你可以创建的字体有两种:库存字体CDC::CreateStockObject( int nIndex )和自定义字体。 在创建非库存字体时需要填充一个LOGFONT结构并使用CFont::CreateFontIndirect(const LOGFONT* lpLogFont ),或使用CFont::CreateFont( int nHeight, int nWidth, int nEscapement, int nOrientation, int nWeight, BYTE bItalic, BYTE bUnderline, BYTE cStrikeOut, BYTE nCharSet, BYTE nOutPrecision, BYTE nClipPrecision, BYTE nQuality, BYTE nPitchAndFamily, LPCTSTR lpszFacename )其中的参数和LOGFONT中的分量有一定的对应关系。下面分别讲解参数的意义: nHeight 字体高度(逻辑单位)等于零为缺省高度,否则取绝对值并和可用的字体高度进行匹配。 nWidth 宽度(逻辑单位)如果为零则使用可用的横纵比进行匹配。 nEscapement 出口矢量与X轴间的角度 nOrientation 字体基线与X轴间的角度 nWeight 字体粗细,可取以下值 Constant Value FW_DONTCARE 0 FW_THIN 100 FW_EXTRALIGHT 200 FW_ULTRALIGHT 200 FW_LIGHT 300 FW_NORMAL 400 FW_REGULAR 400 FW_MEDIUM 500 FW_SEMIBOLD 600 FW_DEMIBOLD 600 FW_BOLD 700 FW_EXTRABOLD 800 FW_ULTRABOLD 800 FW_BLACK 900 FW_HEAVY 900 bItalic 是否为斜体 bUnderline 是否有下划线 cStrikeOut 是否带删除线 nCharSet 指定字符集合,可取以下值 Constant Value ANSI_CHARSET 0 DEFAULT_CHARSET 1 SYMBOL_CHARSET 2 SHIFTJIS_CHARSET 128 OEM_CHARSET 255 nOutPrecision 输出精度 OUT_CHARACTER_PRECIS OUT_STRING_PRECIS OUT_DEFAULT_PRECIS OUT_STROKE_PRECIS OUT_DEVICE_PRECIS OUT_TT_PRECIS OUT_RASTER_PRECIS nClipPrecision 剪辑精度,可取以下值 CLIP_CHARACTER_PRECIS CLIP_MASK CLIP_DEFAULT_PRECIS CLIP_STROKE_PRECIS CLIP_ENCAPSULATE CLIP_TT_ALWAYS CLIP_LH_ANGLES nQuality 输出质量,可取以下值 • DEFAULT_QUALITY Appearance of the font does not matter. • DRAFT_QUALITY Appearance of the font is less important than when PROOF_QUALITY is used. For GDI raster fonts, scaling is enabled. Bold, italic, underline, and strikeout fonts are synthesized if necessary. • PROOF_QUALITY Character quality of the font is more important than exact matching of the logical-font attributes. For GDI raster fonts, scaling is disabled and the font closest in size is chosen. Bold, italic, underline, and strikeout fonts are synthesized if necessary. nPitchAndFamily 字体间的间距 lpszFacename 指定字体名称,为了得到系统所拥有的字体可以利用EmunFontFamiliesEx。 此外可以利用CFontDialog来得到用户选择的字体的LOGFONT数据。 最后我讲一下文本坐标的计算,利用CDC::GetTextExtent( const CString& str )可以得到字符串的在输出时所占用的宽度和高度,这样就可以在手工输出多行文字时使用正确的行距。另外如果需要更精确的对字体高度和宽度进行计算就需要使用CDC::GetTextMetrics( LPTEXTMETRIC lpMetrics ) 该函数将会填充TEXTMETRIC结构,该结构中的分量可以非常精确的描述字体的各种属性。 2.3 使用点,刷子,笔进行绘图 在Windows中画点的方法很简单,只需要调用COLORREF CDC::SetPixel( int x, int y, COLORREF crColor )就可以在指定点画上指定颜色,同时返回原来的颜色。COLORREF CDC::GetPixel( int x, int y)可以得到指定点的颜色。在Windows中应该少使用画点的函数,因为这样做的执行效率比较低。 刷子和画笔在Windows作图中是使用最多的GUI对象,本节在讲解刷子和画笔使用方法的同时也讲述一写基本作图函数。 在画点或画线时系统使用当前DC中的画笔,所以在创建画笔后必须将其选入DC才会在绘图时产生效果。画笔可以通过CPen对象来产生,通过调用CPen::CreatePen( int nPenStyle, int nWidth, COLORREF crColor )来创建。其中nPenStyle指名画笔的风格,可取如下值: • PS_SOLID 实线 Creates a solid pen. • PS_DASH 虚线,宽度必须为一 Creates a dashed pen. Valid only when the pen width is 1 or less, in device units. • PS_DOT 点线,宽度必须为一 Creates a dotted pen. Valid only when the pen width is 1 or less, in device units. • PS_DASHDOT 点划线,宽度必须为一 Creates a pen with alternating dashes and dots. Valid only when the pen width is 1 or less, in device units. • PS_DASHDOTDOT 双点划线,宽度必须为一 Creates a pen with alternating dashes and double dots. Valid only when the pen width is 1 or less, in device units. • PS_NULL 空线,使用时什么也不会产生 Creates a null pen. • PS_ENDCAP_ROUND 结束处为圆形 End caps are round. • PS_ENDCAP_SQUARE 结束处为方形 End caps are square. nWidth和crColor为线的宽度和颜色。 刷子是在画封闭曲线时用来填充的颜色,例如当你画圆形或方形时系统会用当前的刷子对内部进行填充。刷子可利用CBrush对象产生。通过以下几种函数创建刷子: • BOOL CreateSolidBrush( COLORREF crColor ); 创建一种固定颜色的刷子 • BOOL CreateHatchBrush( int nIndex, COLORREF crColor ); 创建指定颜色和网格的刷子,nIndex可取以下值: • HS_BDIAGONAL Downward hatch (left to right) at 45 degrees • HS_CROSS Horizontal and vertical crosshatch • HS_DIAGCROSS Crosshatch at 45 degrees • HS_FDIAGONAL Upward hatch (left to right) at 45 degrees • HS_HORIZONTAL Horizontal hatch • HS_VERTICAL Vertical hatch • BOOL CreatePatternBrush( CBitmap* pBitmap ); 创建以8*8位图为模板的刷子 在选择了画笔和刷子后就可以利用Windows的作图函数进行作图了,基本的画线函数有以下几种 • CDC::MoveTo( int x, int y ); 改变当前点的位置 • CDC::LineTo( int x, int y ); 画一条由当前点到参数指定点的线 • CDC::BOOL Arc( LPCRECT lpRect, POINT ptStart, POINT ptEnd ); 画弧线 • CDC::BOOL Polyline( LPPOINT lpPoints, int nCount ); 将多条线依次序连接 基本的作图函数有以下几种: • CDC::BOOL Rectangle( LPCRECT lpRect ); 矩形 • CDC::RoundRect( LPCRECT lpRect, POINT point ); 圆角矩形 • CDC::Draw3dRect( int x, int y, int cx, int cy, COLORREF clrTopLeft, COLORREF clrBottomRight ); 3D边框 • CDC::Chord( LPCRECT lpRect, POINT ptStart, POINT ptEnd ); 扇形 • CDC::Ellipse( LPCRECT lpRect ); 椭圆形 • CDC::Pie( LPCRECT lpRect, POINT ptStart, POINT ptEnd ); • CDC::Polygon( LPPOINT lpPoints, int nCount ); 多边形 对于矩形,圆形或类似的封闭曲线,系统会使用画笔绘制边缘,使用刷子填充内部。如果你不希望填充或是画出边缘,你可以选入空刷子(NULL_PEN)或是(NULL_BRUSH)空笔。 下面的代码创建一条两象素宽的实线并选入DC。并进行简单的作图: { ... CPen pen; pen.CreatePen(PS_SOLID,2,RGB(128,128,128)); CPen* pOldPen=(CPen*)dc.SelectObject(&pen); dc.SelectStockObject(NULL_BRUSH);//选入空刷子 dc.Rectangle(CRect(0,0,20,20));//画矩形 ... } 2.4 在窗口中绘制设备相关位图,图标,设备无关位图 在Windows中可以将预先准备好的图像复制到显示区域中,这种内存拷贝执行起来是非常快的。在Windows中提供了两种使用图形拷贝的方法:通过设备相关位图(DDB)和设备无关位图(DIB)。 DDB可以用MFC中的CBitmap来表示,而DDB一般是存储在资源文件中,在加载时只需要通过资源ID号就可以将图形装入。BOOL CBitmap::LoadBitmap( UINT nIDResource )可以装入指定DDB,但是在绘制时必须借助另一个和当前绘图DC兼容的内存DC来进行。通过CDC::BitBlt( int x, int y, int nWidth, int nHeight, CDC* pSrcDC, int xSrc, int ySrc, DWORD dwRop )绘制图形,同时指定光栅操作的类型。BitBlt可以将源DC中位图复制到目的DC中,其中前四个参数为目的区域的坐标,接下来是源DC指针,然后是源DC中的起始坐标,由于BitBlt为等比例复制,所以不需要再次指定长宽,(StretchBlt可以进行缩放)最后一个参数为光栅操作的类型,可取以下值: • BLACKNESS 输出区域为黑色 Turns all output black. • DSTINVERT 反色输出区域 Inverts the destination bitmap. • MERGECOPY 在源和目的间使用AND操作 Combines the pattern and the source bitmap using the Boolean AND operator. • MERGEPAINT 在反色后的目的和源间使用OR操作 Combines the inverted source bitmap with the destination bitmap using the Boolean OR operator. • NOTSRCCOPY 将反色后的源拷贝到目的区 Copies the inverted source bitmap to the destination. • PATINVERT 源和目的间进行XOR操作 Combines the destination bitmap with the pattern using the Boolean XOR operator. • SRCAND 源和目的间进行AND操作 Combines pixels of the destination and source bitmaps using the Boolean AND operator. • SRCCOPY 复制源到目的区 Copies the source bitmap to the destination bitmap. • SRCINVERT 源和目的间进行XOR操作 Combines pixels of the destination and source bitmaps using the Boolean XOR operator. • SRCPAINT 源和目的间进行OR操作 Combines pixels of the destination and source bitmaps using the Boolean OR operator. • WHITENESS 输出区域为白色 Turns all output white. 下面用代码演示这种方法: CYourView::OnDraw(CDC* pDC) { CDC memDC;//定义一个兼容DC memDC.CreateCompatibleDC(pDC);//创建DC CBitmap bmpDraw; bmpDraw.LoadBitmap(ID_BMP) ;//装入DDB CBitmap* pbmpOld=memDC.SelectObject(&bmpDraw) ; //保存原有DDB,并选入新DDB入DC pDC->BitBlt(0,0,20,20,&memDC,0,0,SRCCOPY) ; //将源DC中(0,0,20,20)复制到目的DC(0,0,20,20) pDC->BitBlt(20,20,40,40,&memDC,0,0,SRCAND); //将源DC中(0,0,20,20)和目的DC(20,20,40,40)中区域进行AND操作 memDC.SelectObject(pbmpOld) ;//选入原DDB } (图标并不是一个GDI对象,所以不需要选入DC)在MFC中没有一个专门的图标类,因为图标的操作比较简单,使用HICON CWinApp::LoadIcon( UINT nIDResource )或是HICON CWinApp::LoadStandardIcon( LPCTSTR lpszIconName ) 装入后就可以利用BOOL CDC::DrawIcon( int x, int y, HICON hIcon )绘制。由于在图标中可以指定透明区域,所以在某些需要使用非规则图形而且面积不大的时候使用图标会比较简单。下面给出简单的代码: OnDraw(CDC* pDC) { HICON hIcon1=AfxGetApp()->LoadIcon(IDI_I1); HICON hIcon2=AfxGetApp()->LoadIcon(IDI_I2); pDC->DrawIcon(0,0,hIcon1); pDC->DrawIcon(0,40,hIcon2); DestroyIcon(hIcon1); DestroyIcon(hIcon2); } 同样在MFC也没有提供一个DIB的类,所以在使用DIB位图时我们需要自己读取位图文件中的头信息,并读入数据,并利用API函数StretchDIBits绘制。位图文件以BITMAPFILEHEADER结构开始,然后是BITMAPINFOHEADER结构和调色版信息和数据,其实位图格式是图形格式中最简单的一种,而且也是Windows可以理解的一种。我不详细讲解DIB位图的结构,提供一个CDib类供大家使用,这个类包含了基本的功能如:Load,Save,Draw。DownLoad CDib 4K 2.5 使用各种映射方式 所谓的映射方式简单点讲就是坐标的安排方式,系统默认的映射方式为MM_TEXT即X坐标向右增加,Y坐标向下增加,(0,0)在屏幕左上方,DC中的每一点就是屏幕上的一个象素。也许你会认为这种方式下是最好理解的,但是一个点和象素对应的关系在屏幕上看来是正常的,但到了打印机上就会很不正常。因为我们作图是以点为单位并且打印机的分辨率远远比显示器高(800DPI 800点每英寸)所以在打印机上图形看起来就会很小。这样就需要为打印另做一套代码而加大了工作量。如果每个点对应0.1毫米那么在屏幕上的图形就会和打印出来的图形一样大小。 通过int CDC::SetMapMode( int nMapMode )可以指定映射方式,可用的有以下几种: • MM_HIENGLISH 每点对应0.001英寸 Each logical unit is converted to 0.001 inch. Positive x is to the right; positive y is up. • MM_HIMETRIC 每点对应0.001毫米 Each logical unit is converted to 0.01 millimeter. Positive x is to the right; positive y is up. • MM_LOENGLISH 每点对应0.01英寸 Each logical unit is converted to 0.01 inch. Positive x is to the right; positive y is up. • MM_LOMETRIC 每点对应0.001毫米 Each logical unit is converted to 0.1 millimeter. Positive x is to the right; positive y is up. • MM_TEXT 象素对应 Each logical unit is converted to 1 device pixel. Positive x is to the right; positive y is down. 以上几种映射默认的原点在屏幕左上方。除MM_TEXT外都为X坐标向右增加,Y坐标向上增加,和自然坐标是一致的。所以在作图是要注意什么时候应该使用负坐标。而且以上的映射都是X-Y等比例的,即相同的长度在X,Y轴上显示的长度都是相同的。 DownLoad Sample 另外的一种映射方式为MM_ANISOTROPIC,这种方式可以规定不同的长宽比例。在设置这中映射方式后必须调用CSize CDC::SetWindowExt( SIZE size )和CSize CDC::SetViewportExt( SIZE size )来设定长宽比例。系统会根据两次设定的长宽的比值来确定长宽比例。下面给出一段代码比较映射前后的长宽比例: OnDraw(CDC* pDC) { CRect rcC1(200,0,400,200); pDC->FillSolidRect(rcC1,RGB(0,0,255)); pDC->SetMapMode(MM_ANISOTROPIC ); CSize sizeO; sizeO=pDC->SetWindowExt(5,5); TRACE("winExt %d %d\n",sizeO.cx,sizeO.cy); sizeO=pDC->SetViewportExt(5,10); TRACE("ViewExt %d %d\n",sizeO.cx,sizeO.cy); CRect rcC(0,0,200,200); pDC->FillSolidRect(rcC,RGB(0,128,0)); } 上面代码在映射后画出的图形将是一个长方形。 DownLoad Sample 最后讲讲视原点(viewport origin),你可以通过调用CPoint CDC::SetViewportOrg( POINT point )重新设置原点的位置,这就相对于对坐标进行了位移。例如你将原点设置在(20,20)那么原来的(0,0)就变成了(-20,-20)。 2.6 多边形和剪贴区域 多边形也是一个GDI对象,同样遵守其他GDI对象的规则,只是通常都不将其选入DC中。在MFC中多边形有CRgn表示。多边形用来表示一个不同与矩形的区域,和矩形具有相似的操作。如:检测某点是否在内部,并操作等。此外还得到一个包含此多边形的最小矩形。下面介绍一下多边形类的成员函数: • CreateRectRgn 由矩形创建一个多边形 • CreateEllipticRgn 由椭圆创建一个多边形 • CreatePolygonRgn 创建一个有多个点围成的多边形 • PtInRegion 某点是否在内部 • CombineRgn 两个多边形相并 • EqualRgn 两个多边形是否相等 在本节中讲演多边形的意义在于重新在窗口中作图时提高效率。因为引发窗口重绘的原因是某个区域失效,而失效的区域用多边形来表示。假设窗口大小为500*400当上方的另一个窗口从(0,0,10,10)移动到(20,20,30,30)这时(0,0,10,10)区域就失效了,而你只需要重绘这部分区域而不是所有区域,这样你程序的执行效率就会提高。 通过调用API函数int GetClipRgn( HDC hdc, HRGN hrgn)就可以得到失效区域,但是一般用不着那么精确而只需得到包含该区域的最小矩形就可以了,所以可以利用int CDC::GetClipBox( LPRECT lpRect )完成这一功能。 第三章 文档视结构 3.1 文档 视图 框架窗口间的关系和消息传送规律 在MFC中M$引入了文档-视结构的概念,文档相当于数据容器,视相当于查看数据的窗口或是和数据发生交互的窗口。(这一结构在MFC中的OLE,ODBC开发时又得到更多的拓展)因此一个完整的应用一般由四个类组成:CWinApp应用类,CFrameWnd窗口框架类,CDocument文档类,CView视类。(VC6中支持创建不带文档-视的应用) 在程序运行时CWinApp将创建一个CFrameWnd框架窗口实例,而框架窗口将创建文档模板,然后有文档模板创建文档实例和视实例,并将两者关联。一般来讲我们只需对文档和视进行操作,框架的各种行为已经被MFC安排好了而不需人为干预,这也是M$设计文档-视结构的本意,让我们将注意力放在完成任务上而从界面编写中解放出来。 在应用中一个视对应一个文档,但一个文档可以包含多个视。一个应用中只用一个框架窗口,对多文档界面来讲可能有多个MDI子窗口。每一个视都是一个子窗口,在单文档界面中父窗口即是框架窗口,在多文档界面中父窗口为MDI子窗口。一个多文档应用中可以包含多个文档模板,一个模板定义了一个文档和一个或多个视之间的对应关系。同一个文档可以属于多个模板,但一个模板中只允许定义一个文档。同样一个视也可以属于多个文档模板。(不知道我说清楚没有) 接下来看看如何在程序中得到各种对象的指针: • 全局函数AfxGetApp可以得到CWinApp应用类指针 • AfxGetApp()->m_pMainWnd为框架窗口指针 • 在框架窗口中:CFrameWnd::GetActiveDocument得到当前活动文档指针 • 在框架窗口中:CFrameWnd::GetActiveView得到当前活动视指针 • 在视中:CView::GetDocument得到对应的文档指针 • 在文档中:CDocument::GetFirstViewPosition,CDocument::GetNextView用来遍历所有和文档关联的视。 • 在文档中:CDocument::GetDocTemplate得到文档模板指针 • 在多文档界面中:CMDIFrameWnd::MDIGetActive得到当前活动的MDI子窗口 一般来讲用户输入消息(如菜单选择,鼠标,键盘等)会先发往视,如果视未处理则会发往框架窗口。所以定义消息映射时定义在视中就可以了,如果一个应用同时拥有多个视而当前活动视没有对消息进行处理则消息会发往框架窗口。 3.2 接收用户输入 在视中接收鼠标输入: 鼠标消息是我们常需要处理的消息,消息分为:鼠标移动,按钮按下/松开,双击。利用ClassWizard可以轻松的添加这几种消息映射,下面分别讲解每种消息的处理。 WM_MOUSEMOVE对应的函数为OnMouseMove( UINT nFlags, CPoint point ),nFlags表明了当前一些按键的消息,你可以通过“位与”操作进行检测。 • MK_CONTROL Ctrl键是否被按下 Set if the CTRL key is down. • MK_LBUTTON 鼠标左键是否被按下 Set if the left mouse button is down. • MK_MBUTTON 鼠标中间键是否被按下 Set if the middle mouse button is down. • MK_RBUTTON 鼠标右键是否被按下 Set if the right mouse button is down. • MK_SHIFT Shift键是否被按下 Set if the SHIFT key is down. point表示当前鼠标的设备坐标,坐标原点对应视左上角。 WM_LBUTTONDOWN/WM_RBUTTONDOWN(鼠标左/右键按下)对应的函数为OnLButtonDown/OnRButtonDown( UINT nFlags, CPoint point )参数意义和OnMouseMove相同。 WM_LBUTTONUP/WM_RBUTTONUP(鼠标左/右键松开)对应的函数为OnLButtonUp/OnRButtonUp( UINT nFlags, CPoint point )参数意义和OnMouseMove相同。 WM_LBUTTONDBLCLK/WM_RBUTTONDBLCLK(鼠标左/右键双击)对应的函数为OnLButtonDblClk/OnRButtonDblClk( UINT nFlags, CPoint point )参数意义和OnMouseMove相同。 下面我用一段伪代码来讲解一下这些消息的用法: 代码的作用是用鼠标拉出一个矩形 global BOOL fDowned;//是否在拉动 global CPoint ptDown;//按下位置 global CPoint ptUp;//松开位置 OnLButtonDown(UINT nFlags, CPoint point) { fDowned=TRUE; ptUp=ptDown=point; DrawRect(); ... } OnMouseMove(UINT nFlags, CPoint point) { if(fDowned) { DrawRect();//恢复上次所画的矩形 ptUp=point; DrawRect();//画新矩形 } } OnLButtonUp(UINT nFlags, CPoint point) { if(fDowned) { DrawRect();//恢复上次所画的矩形 ptUp=point; DrawRect();//画新矩形 fDowned=FALSE; } } DrawRect() {//以反色屏幕的方法画出ptDown,ptUp标记的矩形 CClientDC dc(this); MakeRect(ptDown,ptUp); SetROP(NOT); Rect(); } 坐标间转换:在以上的函数中point参数对应的都是窗口的设备坐标,我们应该将设备坐标和逻辑坐标相区别,在图32_g1由于窗口使用了滚动条,所以传入的设备坐标是对应于当前窗口左上角的坐标,没有考虑是否滚动,而逻辑坐标必须考虑滚动后对应的坐标,所以我以黄线虚拟的表达一个逻辑坐标的区域。可以看得出同一点在滚动后的坐标值是不同的,这一规则同样适用于改变了映射方式的窗口,假设你将映射方式设置为每点为0.01毫米,那么设备坐标所对应的逻辑坐标也需要重新计算。进行这种转换需要写一段代码,所幸的是系统提供了进行转换的功能DC的DPtoLP,LPtoDP,下面给出代码完成由设备坐标到逻辑坐标的转换。 图32_g1 CPoint CYourView::FromDP(CPoint point) { CClientDC dc(this); CPoint ptRet=point; dc.PrepareDC();//必须先准备DC,这在使用滚动时让DC重新计算坐标 //如果你作图设置了不同的映射方式,则在下面需要设置 dc.SetMapMode(...) // dc.DPtoLP(&ptRet);//DP->LP进行转换 return ptRet; } 在图32_g1中以蓝线标记的是屏幕区域,红线标记的客户区域。利用ScreenToClient,ClientToScreen可以将坐标在这两个区域间转换。 在视中接收键盘输入: 键盘消息有三个:键盘被按下/松开,输入字符。其中输入字符相当于直接得到用户输入的字符这在不需要处理按键细节时使用,而键盘被按下/松开在按键状态改变时发送。 WM_CHAR对应的函数为OnChar( UINT nChar, UINT nRepCnt, UINT nFlags ),其中nChar为被按下的字符,nRepCnt表明在长时间为松开时相当于的按键次数,nFlags中的不同位代表不同的含义,在这里一般不使用。 WM_KEYDOWN/WM_KEYUP所对应的函数为OnKeyDown/OnKeyUp( UINT nChar, UINT nRepCnt, UINT nFlags )nChar代表按键的虚拟码值,如VK_ALT为ALT键,VK_CONTROL为Ctrl键。nFlags各位的含义如下: Value Description 0? Scan code (OEM-dependent value). 8 Extended key, such as a function key or a key on the numeric keypad (1 if it is an extended key). 9?0 Not used. 11?2 Used internally by Windows. 13 Context code (1 if the ALT key is held down while the key is pressed; otherwise 0). 14 Previous key state (1 if the key is down before the call, 0 if the key is up). 15 Transition state (1 if the key is being released, 0 if the key is being pressed). 3.3 使用菜单 利用菜单接受用户命令是一中很简单的交互方法,同时也是一种很有效的方法。通常菜单作为一中资源存储在文件中,因此我们可以在设计时就利用资源编辑器设计好一个菜单。关于使用VC设计菜单我就不再多讲了,但你在编写菜单时应该尽量在属性对话框的底部提示(Prompt)处输入文字,这虽然不是必要的,但MFC在有状态栏和工具条的情况下会使用该文字,文字的格式为“状态栏出说明\n工具条提示”。 图33_g1 我们要面临的任务是如何知道用户何时选择了菜单,他选的是什么菜单项。当用户选择了一个有效的菜单项时系统会向应用发送一个WM_COMMAND消息,在消息的参数中表明来源。在MFC中我们只需要进行一次映射,将某一菜单ID映射到一处理函数,图33_g2。在这里我们在CView的派生类中处理菜单消息,同时我对同一ID设置两个消息映射,接下来将这两种映射的作用。 图33_g2 ON_COMMAND 映射的作用为在用户选择该菜单时调用指定的处理函数。如:ON_COMMAND(IDM_COMMAND1, OnCommand1)会使菜单被选择时调用OnCommand1成员函数。 ON_UPDATE_COMMAND_UI(IDM_COMMAND1, OnUpdateCommand1) 映射的作用是在菜单被显示时通过调用指定的函数来进行确定其状态。在这个处理函数中你可以设置菜单的允许/禁止状态,其显示字符串是什么,是否在前面打钩。函数的参数为CCmdUI* pCmdUI,CCmdUI是MFC专门为更新命令提供的一个类,你可以调用 • Enable 设置允许/禁止状态 • SetCheck 设置是否在前面打钩 • SetText 设置文字 下面我讲解一个例子:我在CView派生类中有一个变量m_fSelected,并且在视中处理两个菜单的消息,当IDM_COMMAND1被选时,对m_fSelected进行逻辑非操作,当IDM_COMMAND2被选中时出一提示;同时IDM_COMMAND1根据m_fSelected决定菜单显示的文字和是否在前面打上检查符号,IDM_COMMAND2根据m_fSelected的值决定菜单的允许/禁止状态。下面是代码和说明:下载示例代码 17K void CMenuDView::OnCommand1() { m_fSelected=!m_fSelected; TRACE("command1 selected\n"); } void CMenuDView::OnUpdateCommand1(CCmdUI* pCmdUI) { pCmdUI->SetCheck(m_fSelected);//决定检查状态 pCmdUI->SetText(m_fSelected?"当前被选中":"当前未被选中");//决定所显示的文字 } void CMenuDView::OnUpdateCommand2(CCmdUI* pCmdUI) {//决定是否为允许 pCmdUI->Enable(m_fSelected); } void CMenuDView::OnCommand2() {//选中时给出提示 AfxMessageBox("你选了command2"); } 接下来再讲一些通过代码操纵菜单的方法,在MFC中有一个类CMenu用来处理和菜单有关的功能。在生成一个CMenu对象时你需要从资源中装如菜单,通过调用BOOL CMenu::LoadMenu( UINT nIDResource )进行装入,然后你就可以对菜单进行动态的修改,所涉及到的函数有: • CMenu* GetSubMenu( int nPos ) 一位置得到子菜单的指针,因为一个CMenu对象只能表示一个弹出菜单,如果菜单中的某一项也为弹出菜单,就需要通过该函数获取指针。 • BOOL AppendMenu( UINT nFlags, UINT nIDNewItem = 0, LPCTSTR lpszNewItem = NULL ) 在末尾添加一项,nFlag为MF_SEPARATOR表示增加一个分隔条,这样其他两个参数将会被忽略;为MF_STRING表示添加一个菜单项uIDNewItem为该菜单的ID命令值;为MF_POPUP表示添加一个弹出菜单项,这时uIDNewItem为另一菜单的句柄HMENU。lpszNewItem为菜单文字说明。 • BOOL InsertMenu( UINT nPosition, UINT nFlags, UINT nIDNewItem = 0, LPCTSTR lpszNewItem = NULL )用于在指定位置插入一菜单,位置由变量nPosition指明。如果nFlags包含MF_BYPOSITION则表明插入在nPosition位置,如果包含MF_BYCOMMAND表示插入在命令ID为nPosition的菜单处。 • BOOL ModifyMenu( UINT nPosition, UINT nFlags, UINT nIDNewItem = 0, LPCTSTR lpszNewItem = NULL )用于修改某一位置的菜单,如果nFlags包含MF_BYPOSITION则表明修改nPosition位置的菜单,如果包含MF_BYCOMMAND表示修改命令ID为nPosition处的菜单。 • BOOL RemoveMenu( UINT nPosition, UINT nFlags )用于删除某一位置的菜单。如果nFlags包含MF_BYPOSITION则表明删除nPosition位置的菜单,如果包含MF_BYCOMMAND表示删除命令ID为nPosition处的菜单。 • BOOL AppendMenu( UINT nFlags, UINT nIDNewItem, const CBitmap* pBmp ) 和 BOOL InsertMenu( UINT nPosition, UINT nFlags, UINT nIDNewItem, const CBitmap* pBmp )可以添加一位图菜单,但这样的菜单在选中时只是反色显示,并不美观。 视图中是没有菜单的,在框架窗口中才有,所以只有用AfxGetApp()->m_pMainWnd->GetMenu()才能得到应用的菜单指针。 最后我讲一下如何在程序中弹出一个菜单,你必须先装入一个菜单资源,你必需得到一个弹出菜单的指针然后调用BOOL TrackPopupMenu( UINT nFlags, int x, int y, CWnd* pWnd, LPCRECT lpRect = NULL )弹出菜单,你需要指定(x,y)为菜单弹出的位置,pWnd为接收命令消息的窗口指针。下面有一段代码说明方法,下载示例代码 17K。当然为了处理消息你应该在pWnd指明的窗口中对菜单命令消息进行映射。 CMenu menu; menu.LoadMenu(IDR_POPUP); CMenu* pM=menu.GetSubMenu(0); CPoint pt; GetCursorPos(&pt); pM->TrackPopupMenu(TPM_LEFTALIGN,pt.x,pt.y,this); 另一种做法是通过CMenu::CreatePopupMenu()建立一个弹出菜单,然后使用TrackPopupMenu弹出菜单。使用CreatePopupMenu创建的菜单也可以将其作为一个弹出项添加另一个菜单中。下面的伪代码演示了如何创建一个弹出菜单并进行修改后弹出: CMenu menu1,menu2; menu1.CreatePopupMenu menu1.InsertMenu(1) menu1.InsertMenu(2) menu1.InsertMenu(3) menu2.CreatePopupMenu menu2.AppendMenu(MF_POPUP,1,menu1.Detach()) 将弹出菜单加入 or InsertMenu... menu2.InsertMenu("string desc"); menu.TrackPopupMenu(...) 3.4 文档,视,框架之间相互作用 一般来说用户的输入/输出基本都是通过视进行,但一些例外的情况下可能需要和框架直接发生作用,而在多视的情况下如何在视之间传递数据。 在使用菜单时大家会发现当一个菜单没有进行映射处理时为禁止状态,在多视的情况下菜单的状态和处理映射是和当前活动视相联系的,这样MFC可以保证视能正确的接收到各种消息,但有时候也会产生不便。有一个解决办法就是在框架中对消息进行处理,这样也可以保证当前文档可以通过框架得到当前消息。 在用户进行输入后如何使视的状态得到更新?这个问题在一个文档对应一个视图时是不存在的,但是现在有一个文档对应了两个视图,当在一个视上进行了输入时如何保证另一个视也得到通知呢?MFC的做法是利用文档来处理的,因为文档管理着当前和它联系的视,由它来通知各个视是最合适的。让我们同时看两个函数: • void CView::OnUpdate( CView* pSender, LPARAM lHint, CObject* pHint ) • void CDocument::UpdateAllViews( CView* pSender, LPARAM lHint = 0L, CObject* pHint = NULL ) 当文档的UpdateAllViews被调用时和此文档相关的所有视的OnUpdate都会被调用,而参数lHint和pHint都会被传递。这样一来发生改变视就可以通知其他的兄弟了。那么还有一个问题:如何在OnUpdate中知道是那个视图发生了改变呢,这就可以利用pHint参数,只要调用者将this指针赋值给参数就可以了,当然完全可以利用该参数传递更复杂的结构。 视的初始化,当一个文档被打开或是新建一个文档时视图的CView::OnInitialUpdate()会被调用,你可以通过重载该函数对视进行初始化,并在结束前调用父类的OnInitialUpdate,因为这样可以保证OnUpdate会被调用。 文档中内容的清除,当文档被关闭时(比如退出或是新建前上一个文档清除)void CDocument::DeleteContents ()会被调用,你可以通过重载该函数来进行清理工作。 在单文档结构中上面两点尤其重要,因为软件运行文档对象和视对象只会被产生并删除一次。所以应该将上面两点和C++对象构造和构析分清楚。 最后将一下文档模板(DocTemplate)的作用,文档模板分为两类单文档模板和多文档模板,分别由CSingleDocTemplate和CMultiDocTemplate表示,模板的作用在于记录文档,视,框架之间的对应关系。还有一点就是模板可以记录应用程序可以打开的文件的类型,当打开文件时会根据文档模板中的信息选择正确的文档和视。模板是一个比较抽想的概念,一般来说是不需要我们直接进行操作的。 当使用者通过视修改了数据时,应该调用GetDocument()->SetModifiedFlag(TRUE)通知文档数据已经被更新,这样在关闭文档时会自动询问用户是否保存数据。 好象这一节讲的有些乱,大家看后有什么想法和问题请在VCHelp论坛上留言,我会尽快回复并且会对本节内容重新整理和修改。 3.5 利用序列化进行文件读写 在很多应用中我们需要对数据进行保存,或是从介质上读取数据,这就涉及到文件的操作。我们可以利用各种文件存取方法完成这些工作,但MFC中也提供了一种读写文件的简单方法——“序列化”。序列化机制通过更高层次的接口功能向开发者提供了更利于使用和透明于字节流的文件操纵方法,举一个例来讲你可以将一个字串写入文件而不需要理会具体长度,读出时也是一样。你甚至可以对字符串数组进行操作。在MFC提供的可自动分配内存的类的支持下你可以更轻松的读/写数据。你也可以根据需要编写你自己的具有序列化功能的类。 序列化在最低的层次上应该被需要序列化的类支持,也就是说如果你需要对一个类进行序列化,那么这个类必须支持序列化。当通过序列化进行文件读写时你只需要该类的序列化函数就可以了。 怎样使类具有序列化功能呢?你需要以下的工作: • 该类从CObject派生。 • 在类声明中包括DECLARE_SERIAL宏定义。 • 提供一个缺省的构造函数。 • 在类中实现Serialze函数 • 使用IMPLEMENT_SERIAL指明类名和版本号 下面的代码建立了一个简单身份证记录的类,同时也能够支持序列化。 in H struct strPID { char szName[10]; char szID[16]; struct strPID* pNext; }; class CAllPID : public CObject { public: DECLARE_SERIAL(CAllPID) CAllPID(); ~CAllPID(); public:// 序列化相关 struct strPID* pHead; //其他的成员函数 void Serialize(CArchive& ar); }; in CPP IMPLEMENT_SERIAL(CAllPID,CObject,1) // version is 1,版本用于读数据时的检测 void CAllPID::Serialize(CArchive& ar) { int iTotal; if(ar.IsStoring()) {//保存数据 iTotal=GetTotalID();//得到链表中的记录数量 arr<26;i++) ar<>iTotal; for(int i=0;i26;j++) ar>>*(((BYTE*)pID)+j);//读一个strPID中所有的数据 //修改链表 } } } 当然上面的代码很不完整,但已经可以说明问题。这样CAllPID就是一个可以支持序列化的类,并且可以根据记录的数量动态分配内存。在序列化中我们使用了CArchive类,该类用于在序列化时提供读写支持,它重载了<>运算符号,并且提供Read和Write函数对数据进行读写。 下面看看如何在文档中使用序列化功能,你只需要修改文档类的Serialize(CArchive& ar)函数,并调用各个进行序列化的类的Serial进行数据读写就可以了。当然你也可以在文档类的内部进行数据读写,下面的代码利用序列化功能读写数据: class CYourDoc : public CDocument { void Serialize(CArchive& ar); CString m_szDesc; CAllPID m_allPID; ...... } void CYourDoc::Serialize(CArchive& ar) { if (ar.IsStoring()) {//由于CString对CArchive定义了<>操作符号,所以可以直接利用>>和<< ar<>m_szDesc; } m_allPID.Serialize(ar);//调用数据类的序列化函数 3.6 MFC中所提供的各种视类介绍 MFC中提供了丰富的视类供开发者使用,下面对各个类进行介绍: CView类是最基本的视类只支持最基本的操作。 CScrollView类提供了滚动的功能,你可以利用void CScrollView::SetScrollSizes( int nMapMode, SIZE sizeTotal, const SIZE& sizePage = sizeDefault, const SIZE& sizeLine = sizeDefault )设置滚动尺寸,和坐标映射模式。但是在绘图和接收用户输入时需要对坐标进行转换。请参见3.2 接收用户输入。 CFormView类提供用户在资源文件中定义界面的能力,并可以将子窗口和变量进行绑定。通过UpdateData函数让数据在变量和子窗口间交换。 CTreeView类利用TreeCtrl界面作为视界面,通过调用CTreeCtrl& CTreeView::GetTreeCtrl( ) const得到CTreeCtrl的引用。 CListView类利用ListCtrl界面作为视界面,通过调用CTreeCtrl& CTreeView::GetTreeCtrl( ) const得到CListCtrl的引用。 CEditView类利用Edit接收用户输入,它具有输入框的一切功能。通过调用CEdit& CEditView::GetEditCtrl( ) const得到Edit&的引用。void CEditView::SetPrinterFont( CFont* pFont )可以设置打印字体。 CRichEditView类作为Rich Text Edit(富文本输入)的视类,提供了可以按照格式显示文本的能力,在使用时需要CRichEditDoc的支持。 第四章 窗口控件 4.1 Button 按钮窗口(控件)在MFC中使用CButton表示,CButton包含了三种样式的按钮,Push Button,Check Box,Radio Box。所以在利用CButton对象生成按钮窗口时需要指明按钮的风格。 创建按钮:BOOL CButton::Create( LPCTSTR lpszCaption, DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID );其中lpszCaption是按钮上显示的文字,dwStyle为按钮风格,除了Windows风格可以使用外(如WS_CHILD|WS_VISUBLE|WS_BORDER)还有按钮专用的一些风格。 • BS_AUTOCHECKBOX 检查框,按钮的状态会自动改变 Same as a check box, except that a check mark appears in the check box when the user selects the box; the check mark disappears the next time the user selects the box. • BS_AUTORADIOBUTTON 圆形选择按钮,按钮的状态会自动改变 Same as a radio button, except that when the user selects it, the button automatically highlights itself and removes the selection from any other radio buttons with the same style in the same group. • BS_AUTO3STATE 允许按钮有三种状态即:选中,未选中,未定 Same as a three-state check box, except that the box changes its state when the user selects it. • BS_CHECKBOX 检查框 Creates a small square that has text displayed to its right (unless this style is combined with the BS_LEFTTEXT style). • BS_DEFPUSHBUTTON 默认普通按钮 Creates a button that has a heavy black border. The user can select this button by pressing the ENTER key. This style enables the user to quickly select the most likely option (the default option). • BS_LEFTTEXT 左对齐文字 When combined with a radio-button or check-box style, the text appears on the left side of the radio button or check box. • BS_OWNERDRAW 自绘按钮 Creates an owner-drawn button. The framework calls the DrawItem member function when a visual aspect of the button has changed. This style must be set when using the CBitmapButton class. • BS_PUSHBUTTON 普通按钮 Creates a pushbutton that posts a WM_COMMAND message to the owner window when the user selects the button. • BS_RADIOBUTTON 圆形选择按钮 Creates a small circle that has text displayed to its right (unless this style is combined with the BS_LEFTTEXT style). Radio buttons are usually used in groups of related but mutually exclusive choices. • BS_3STATE 允许按钮有三种状态即:选中,未选中,未定 Same as a check box, except that the box can be dimmed as well as checked. The dimmed state typically is used to show that a check box has been disabled. rect为窗口所占据的矩形区域,pParentWnd为父窗口指针,nID为该窗口的ID值。 获取/改变按钮状态:对于检查按钮和圆形按钮可能有两种状态,选中和未选中,如果设置了BS_3STATE或BS_AUTO3STATE风格就可能出现第三种状态:未定,这时按钮显示灰色。通过调用int CButton::GetCheck( ) 得到当前是否被选中,返回0:未选中,1:选中,2:未定。调用void CButton::SetCheck( int nCheck );设置当前选中状态。 处理按钮消息:要处理按钮消息需要在父窗口中进行消息映射,映射宏为ON_BN_CLICKED( id, memberFxn )id为按钮的ID值,就是创建时指定的nID值。处理函数原型为afx_msg void memberFxn( ); 4.2 Static Box 静态文本控件的功能比较简单,可作为显示字符串,图标,位图用。创建一个窗口可以使用成员函数: BOOL CStatic::Create( LPCTSTR lpszText, DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID = 0xffff ); 其中dwStyle将指明该窗口的风格,除了子窗口常用的风格WS_CHILD,WS_VISIBLE外,你可以针对静态控件指明专门的风格。 • SS_CENTER,SS_LEFT,SS_RIGHT 指明字符显示的对齐方式。 • SS_GRAYRECT 显示一个灰色的矩形 • SS_NOPREFIX 如果指明该风格,对于字符&将直接显示,否则&将作为转义符,&将不显示而在其后的字符将有下划线,如果需要直接显示&必须使用&&表示。 • SS_BITMAP 显示位图 • SS_ICON 显示图标 • SS_CENTERIMAGE 图象居中显示 控制显示的文本利用成员函数SetWindowText/GetWindowText用于设置/得到当前显示的文本。 控制显示的图标利用成员函数SetIcon/GetIcon用于设置/得到当前显示的图标。 控制显示的位图利用成员函数SetBitmap/GetBitmap用于设置/得到当前显示的位图。下面一段代码演示如何创建一个显示位图的静态窗口并设置位图 CStatic* pstaDis=new CStatic; pstaDis->Create("",WS_CHILD|WS_VISIBLE|SS_BITMAP|SSCENTERIMAGE, CRect(0,0,40,40),pWnd,1); CBitmap bmpLoad; bmpLoad.LoadBitmap(IDB_TEST); pstaDis->SetBitmap(bmpLoad.Detach()); 4.3 Edit Box Edit窗口是用来接收用户输入最常用的一个控件。创建一个输入窗口可以使用成员函数: BOOL CEdit::Create( LPCTSTR lpszText, DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID = 0xffff ); 其中dwStyle将指明该窗口的风格,除了子窗口常用的风格WS_CHILD,WS_VISIBLE外,你可以针对输入控件指明专门的风格。 • ES_AUTOHSCROLL,ES_AUTOVSCROLL 指明输入文字超出显示范围时自动滚动。 • ES_CENTER,ES_LEFT,ES_RIGHT 指定对齐方式 • ES_MULTILINE 是否允许多行输入 • ES_PASSWORD 是否为密码输入框,如果指明该风格则输入的文字显示为* • ES_READONLY 是否为只读 • ES_UPPERCASE,ES_LOWERCASE 显示大写/小写字符 控制显示的文本利用成员函数SetWindowText/GetWindowText用于设置/得到当前显示的文本。 通过GetLimitText/SetLimitText可以得到/设置在输入框中输入的字符数量。 由于在输入时用户可能选择某一段文本,所以通过void CEdit::GetSel( int& nStartChar, in

33,311

社区成员

发帖
与我相关
我的任务
社区描述
C/C++ 新手乐园
社区管理员
  • 新手乐园社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧