资源描述
1、sprintf 函数操作的对象不限于字符串:虽然目标对象是字符串,不过源对象
能够是字符串、也能够是任意基本类型的数据。这个函数重要用来实现(字符串
或基本数据类型)向字符串的转换功效。假如源对象是字符串,并且指定 %s 格
式符,也可实现字符串拷贝功效。
2、请编写一个 C 函数,该函数给出一个字节中被置 1 的位的个数,并请给出该
题的最少一个不一样解法。
unsigned int TestAsOne0(char log)
{ int i;
unsigned int num=0, val;
for(i=0; i<8; i++)
{ val = log >> i; //移位
val &= 0x01; //与1相与
if(val)
num++;
}
return num;
}
3、请编写一个 C 函数,该函数将给定的一个字符串转换成整数。
int Invert(char *str)
{ int num=0;
while(*str!='\0')
{ int digital=*str-48;
num=num*10+digital;
str=str+1;
}
return num;
}
4、请编写一个 C 函数,该函数将给定的一个整数转换成字符串。
void IntToCharChange(int num, char* pval)
{ char strval[100];
int i, j;
int val0 = 0;
int val1 = 0;
val0 = num;
for(i=0; i<100; i++)
{ val1 = val0 % 10; //取余
val0 = val0 / 10; //取整
strval[i] = val1 + 48; //数字—字符
if(val0 < 10)
{ i++;
strval[i] = val0 + 48;
break;
}
}
for(j=0; j<=i; j++) //倒置
{ pval[j] = strval[i-j];
}
pval[j] = '\0';
}
5、请编写一个 C 函数,该函数将一个字符串逆序。
void AntitoneValue(char* father, char* child)
{ int i;
char source[100];
int j = 0;
while(father[j]) //放入source,[j]为长度
{ source[j] = father[j];
j++;
if(j > 99)
{ return;
}
}
source[j] = '\0';
for(i=0; i<j; i++)
{ child[i] = source[j-i-1]; //反序
}
child[i] = '\0';
}
6、请编写一个 C 函数,该函数在给定的内存区域搜索给定的字符,并返回该字
符所在位置索引值。
int search(char *cpSource, int n, char ch) //起始地址,搜索长度,目标字符
{int i;
for(i=0; i<n && *(cpSource+i) != ch; ++i);
return i;
}
7、请编写一个 C 函数,该函数在一个字符串中找到也许的最长的子字符串,该
字符串是由同一字符组成的。
int ChildString(char*p) //自己写
{ Char *q=p;
int stringlen=0, i=0,j=1,len=0,maxlen=1;
while(*q!=’\0’) //不能用strlen,求得长度stringlen
{ Stringlen++;
q++;
}
while( i< Stringlen )
{ if(*(p+i)==*(p+j)&&j< Stringlen)
{ len++; //统计子串长度
i++;
j++;
}
else
{ if(len>maxlen) //统计最大子串长度
{ maxlen=len+1;
len=0;
}
else { len=0;
}
i++;
j++;
}
}
return maxlen;
}
8、用<<,>>,|,&实现一个WORD(2个字节)的高低位互换!!
int main()
{ unsigned short a = 0xABCD;
unsigned short b ;
unsigned short c ,d;
b = (a << 8)&0xff00;
c = (a >> 8)&0x00ff;
d = b | c;
printf("\n%x",b);
printf("\n%x",c);
printf("\n%x",d);
return 0;
}
成果是 CDAB
2俩个字节是16位 前八位为高位 后八位为低位 然后结合
9、编写一个函数,函数接收一个字符串,是由十六进制数组成的一组字符串,函数
的功效是把接到的这组字符串转换成十进制数字.并将十进制数字返回.
答案:BOOL HexToDec( LPCTSTR shex,int& idec )
{ int i,mid;
int len = lstrlen( shex );
if( len>8 )
return FALSE;
mid = 0; idec = 0;
for( i=0;i<len;i++ )
{ if( shex[i]>='0'&&shex[i]<='9' )
mid = shex[i]-'0';
else if( shex[i]>='a'&&shex[i]<='f' )
mid = shex[i] -'a' +10;
else if( shex[i]>='A'&&shex[i]<='F' )
mid = shex[i] -'A' +10;
else
return FALSE;
mid <<= ((len-i-1)<<2); // 移位表示变为2的n次方倍
idec =idc+mid;
}
return TRUE;
}
10、 #include “filename.h”和#include <filename.h>的区分?
对于#include <filename.h>编译器从标准库开始搜索filename.h;对于#include “filename.h”编译器从用户工作途径开始搜索filename.h
11、 头文献的作用是什么?
用户只需要按照头文献中的接口申明来调用库功效,而无须关心接口怎么实现的。编译器会从库中提取对应的代码。头文献能加强类型安全检查。假如某个接口被实现或被使用时,其方式与头文献中的申明不一致,编译器就会指犯错误,这一简单的规则能大大减轻程序员调试、改错的负担。
12、C++函数中值的传递方式有哪几个?
C++函数的三种传递方式为:值传递、指针传递和引用传递。
13、内存的分派方式的分派方式有几个?
答:一、从静态存储区域分派。内存在程序编译的时候就已经分派好,这块内存在程序的整个运行期间都存在。例如全局变量。
二、在栈上创建。在执行函数时,函数内局部变量的存储单元都能够在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分派运算内置于处理器的指令集中,效率很高,不过度派的内存容量有限。
三、从堆上分派,亦称动态内存分派。程序在运行的时候用malloc或new申请任意多少的内存,程序员自己负责在何时用free或delete释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多。
14、实现双向链表删除一个节点P,在节点P后插入一个节点,写出这两个函数;
//删除操作
Status ListDelete_DuL(DuLinkList &L,int i,ElemType &e)
{ if(!(p=GetElemP_DuL(L,i))) //此处得到i位置的节点指针,假如有需要也得写出详细函数实现
return ERROR;
e=p->data;
p->prior->next=p->next;
p->next->prior=p->pror;
free(p);
return OK;
}
//插入操作
Status ListInsert_DuL(DuLinkList &L,int i,ElemType &e)
{ if(!(p=GetElemP_DuL(L,i)))
return ERROR;
if(!(s=(DuLinkList)malloc(sizeof(DuLNode)))) //生成新节点
return ERROR;
s->data=e;
s->prior=p;
p-> next -> prior =s;
p->next=s;
s->next=p->next->next;
return OK;
}
15、请讲一讲析构函数和虚函数的使用方法和作用?
析构函数是特殊的类组员函数,它没有返回类型,没有参数,不能随意调用,也没有重载,只有在类对象的生命期结束的时候,由系统自动调用。 有适放内存空间的做用。
虚函数是C++多态的一个体现, 使用虚函数,我们能够灵活的进行动态绑定,当然是以一定的开销为代价。
16、全局变量和局部变量有什么区分?实怎么实现的?操作系统和编译器是怎么懂得的?
生命周期不一样:全局变量随主程序创建和创建,随主程序销毁而销毁局部变量在局部函数内部,甚至局部循环体等内部存在,退出就不存在; 内存中分派在全局数据区使用方式不一样:通过申明后全局变量程序的各个部分都能够用到局部变量只能在局部使用;分派在栈区操作系统和编译器通过内存分派的位置来懂得的,全局变量分派在全局数据段并且在程序开始运行的时候被加载。局部变量则分派在堆栈里面 。
17、多态。overload 和 override的区分。
overload是重载,重载是一个参数多态机制,即代码通过参数的类型或个数不一样而实现的多态机制。 是一个静态的绑定机制(在编译时已经懂得详细执行的是哪个代码段)。 override是覆盖。覆盖是一个动态绑定的多态机制。即在父类和子类中同名元素(如组员函数)有不一样的实当代码。执行的是哪个代码是依照运行时实际情况而定的。
重载Overload特点
public bool withdraw(double amt, string name)
public double withdraw(double amt)
1、措施名必须相同2、参数列表必须不相同3、返回值类型能够不相同
注意:override存在于继继承的关系类中。
覆写Override特点(三相同):
public override bool withdraw(...)
1、措施名相同2、参数列表相同3、返回值类型相同
注意:存在于同一类中,不过只有虚措施和抽象措施才能被覆写.
18、解释堆和栈的区分。
在老式的C中堆和栈实际是一块物理内存,堆重要用来动态分派内存,从堆栈内存的低端向上分派;而栈重要用来传递函数参数、返回值和局部参数内存分派,是从堆栈内存的高品位向下分派,俗称压栈和出栈;堆是动态分派,例如用new,malloc分派,需要手工释放,否则会导致memory leak, 栈是静态分派,例如函数调用是需要分派堆栈,但堆栈能自动释放.
19、论述含参数的宏与函数的优缺陷。
宏是编译期的,函数是运行期的;宏不是实体,而函数是一个可寻址的实体;宏只是编译期替代,在程序里每遇到S(a,b),就用a*b替代,a和b两个实体并没有由宏实际产生,而函数S会在栈中定义两个对象a和b。宏没有生存期、作用域之类的概念,而函数就有。
20、怎样用最快的措施判断链表是否有环?
用两个指针来遍历这个单向链表,第一个指针p1,每次走一步;第二个指针p2,
每次走两步; 当p2 指针追上 p1的时候,就表白链表当中有环路了。
怎么判断链表中是否有环?
bool CircleInList(Link* pHead)
{if(pHead = = NULL || pHead->next = = NULL)//无节点或只有一个节点并且无自环
return (false);
if(pHead->next = = pHead)//自环
return (true);
Link *pTemp1 = pHead; //step 1
Link *pTemp = pHead->next; //step 2
while(pTemp != pTemp1 && pTemp != NULL && pTemp->next != NULL)
{pTemp1 = pTemp1->next; //增量1
pTemp = pTemp->next->next; //增量2
}
if(pTemp = = pTemp1)
return (true);
return (false);
}
21、请问互换机和路由器各自的实现原理是什么?分别在哪个层次上面实现的?
互换机(二层互换)的工作原理和网桥同样,它的各个端口都具备桥接功效,每个端口能够连接一个LAN或一台高性能网站或服务器,互换机按每一个包中的MAC地址相对简单地决议信息转发。//路由器转发IP分组时,只依照IP分组目标IP地址的网络号部分,选择适宜的端口,把IP分组送出去。同主机同样,路由器也要判定端口所接的是否是目标子网,假如是,就直接把分组通过端口送到网络上,否则,也要选择下一个路由器来传送分组。互换机一般用于LAN-WAN的连接,互换机归于网桥,是数据链路层的设备,有些互换机也可实现第三层的互换。路由器用于WAN-WAN之间的连接,能够处理异性网络之间转发分组,作用于网络层。
22、全局变量和局部变量有什么区分?是怎么实现的?操作系统和编译器是怎么懂得的?
答:某些变量在整个程序中都是可见的,它们称为全局变量。某些变量只能在一个函数中可知,称为局部变量。这就是他们的区分。在任何函数外面定义的变量就是全局变量,在函数内部定义的变量是局部变量,这是它们在程序中的实现过程。操作系统和编译器是依照程序运行的内存区域懂得他们的,程序的全局数据放在所分派内存的全局数据区,程序的局部数据放在栈区。
23、把一个字符串倒序,如“abcd”倒序后变为“dcba”
措施1:int main(){
char* src = "hello,world";
int len = strlen(src);
char* dest = (char*)malloc(len+1);//要为\0分派一个空间
char* d = dest;
char* s = &src[len-1];//指向最后一个字符
while( len-- != 0 )
*d++=*s--;
*d = 0;//尾部要加\0
printf("%s\n",dest);
free(dest);// 使用完,应当释放空间,以免导致内存汇泄露
return 0;
}
措施2:#include <stdio.h>
#include <string.h>
main()
{char str[]="hello,world";
int len=strlen(str);
char t;
for(int i=0; i<len/2; i++)
{t=str[i];
str[i]=str[len-i-1]; str[len-i-1]=t;
}
printf("%s",str);
return 0;
}
24、.在c语言库函数中将一个字符转换成整型的函数是atool()吗,这个函数的原型是什么?
函数名: atol
功 能: 把字符串转换成长整型数
用 法: long atol(const char *nptr);
程序例: #include <stdlib.h>
#include <stdio.h>
int main(void)
{ long l;
char *str = "98765432";
l = atol(lstr);
printf("string = %s integer = %ld\n", str, l);
return(0);
}
25、软件测试都有那些种类?
黑盒:针对系统功效的测试 白盒:测试函数功效,各函数接口
26、
OSI中的层
功效
TCP/IP协议族
应用层
文献传输,电子邮件,文献服务,虚拟终端
TFTP,HTTP,SNMP,FTP,SMTP,DNS,Telnet
表示层
数据格式化,代码转换,数据加密
没有协议
会话层
解除或建立与别的接点的联系
没有协议
传输层
提供端对端的接口
TCP,UDP
网络层
为数据包选择路由
IP,ICMP,RIP,OSPF,BGP,IGMP
数据链路层
传输有地址的帧以及错误检测功效
SLIP,CSLIP,PPP,ARP,RARP,MTU
物理层
以二进制数据形式在物理媒体上传输数据
ISO2110,IEEE802。IEEE802.2
27、TCP/IP通信建立的过程怎样,端口有什么作用?三次握手
A->B SYN:我的初始序号是X,ACK是0,设置SYN位,未设置ACK位。
B->A ACK:你的序号是X+1,我的初始序号是Y,设置SYN位和ACK位。
A->B ACK:你的序号是Y+1,我的序号是X+1,设置ACK位,未设置SYN位;端口确定是哪个应用程序使用该协议
28、已知一个单向链表的头,请写出删除其某一个结点的算法,要求,先找到此结点,然后删除。
slnodetype *Delete(slnodetype *Head,int key){}中if(Head->number==key)
{Head=Pointer->next;
free(Pointer);
break;
}
Back = Pointer;
Pointer=Pointer->next;
if(Pointer->number==key)
{Back->next=Pointer->next;
free(Pointer);
break;
}
void delete(Node* p)
{if(Head = Node)
while(p)
}
29、1号信令和7号信令有什么区分,我国某前广泛使用的是那一个?
1号信令利用TS16传送时,每个TS16负责传送两个话路的线路信令,TS16和话路有着固定的一一对应关系。而7号信令利用TS16来传送时,只是将组成信令单元的若干个8位位组,依次插入TS16,TS16并不懂得传送的内容,即信令和话路没有固定关系,只不过利用TS16作为传送信令的载体,时传送信令消息的数据链路,因此,选用哪个时隙做数据链路均可。 --- 这也是随路信令和公共信道信令的一个本质区分。我国目前广泛使用的是7号信令。
30、已知一个单向链表的头,请写出删除其某一个结点的算法,要求,先找到此结点,然后删除。
slnodetype *Delete(slnodetype *Head,int key){}中if(Head->number==key)
{Head=Pointer->next;
free(Pointer);
break;
}
Back = Pointer;
Pointer=Pointer->next;
if(Pointer->number==key)
{Back->next=Pointer->next;
free(Pointer);
break;
}
void delete(Node* p)
{if(Head = Node)
while(p)
}
31、写出程序把一个链表中的接点次序倒排
typedef struct linknode
{int data;
struct linknode *next;
}node;
//将一个链表逆置
node *reverse(node *head)
{node *p,*q,*r;
p=head;
q=p->next;
while(q!=NULL)
{ r=q->next;
q->next=p;
p=q;
q=r;}
head->next=NULL;
head=p;
return head;
}
32、写出程序删除链表中的所有接点
void del_all(node *head)
{ node *p;
while(head!=NULL)
{ p=head->next;
free(head);
head=p;
}
cout<<"释放空间成功!"<<endl;
}
33、什么是中断?中断发生时CPU做什么工作?
所谓中断是指系统发生某一事件后,CPU暂停正在执行的程序转去执行处理该事件的程序过程,处理中断事件的程序称为中断处理程序,产生中断信号的那个部件称为中断源。硬件的中断机构与处理这些中断的程序统称为中断系统。
当中断发生时,硬件机构自动地进入响应中断过程,由操作系统的中断处理程序对中断事件进行处理,详细过程如下:
①·保存现场
系统开辟现场区,并将现场区组织成"栈"结构,当中断响应时,(1)硬件结构自动将PS和PC存储器的内容压人栈中作为现场信息保存起来。(2)依照发生的中断,硬件从指定的中断向量单元中取出PS和PC内容,分别装人PS和PC存储器,同时正确填人路存储器的"目前状态"和"先前状态"字段。
②·分析原因,转中断处理程序
不一样原因产生的中断事件要进行不一样的处理,依照中断的路存储器内容得出发生该种中断的详细原因。转人相对应的申断处理程序运行。
③·恢复现场
在多级中断系统中,考虑退回目前中断时,必须依据原先被中断的程序,完成不一样的工作,中断处理结柬后,软件必须退出中断。假如本次是高级中断,并且被中断的程序是一个低级中断处理程序,则本次中断应返回到该低级中断处理程序。假如本来被中断的是用户程序,则退出中断前应先考虑进行一次调度选择,以挑选出更适合在目前情况下运行的新程序。
34、CPU在上电后,进入操作系统的main()之前必须做什么工作?
整个系统对开发环境以及各种变量的初始化,包括了变量空间的分派,cpu内部存储器的初始化,总线的初始化等等,总之,只有等系统初始化完成以后,我们的c语言的main才能被识别和执行下来
35、试编写3个函数实现
(1)建立一个双向链表
(2)插入一个节点
(3)删除一个节点
typedef struct duLNode
{int data;
struct duLNode *prior;
struct duLNode *next;
}duLNode,*dulinklist;
Status Intilsit_DuL(dulinklist &l)//初始化
{if(!(l=(dulinklist)malloc(sizeof(duLNode))))
return ERROR;
l->next=NULL;
l->prior=NULL;
l->data=0; //利用头接点的数据域存储表的长度
return OK;
}
常规插入操作:
status listinsert_dul(dulinklist &l,int i elemtype e) {
if (! (p=getelemp_dul (l,i))) return error;
if (!(s=(dulinklist) malloc (sizeof (dul node)))) return error;
s->data=e;
s->next=p->prior;
s->next=p;
p->prior->next=s;
p->priot=s;
return ok;
}
删除操作
status listdelete_dul (dulinklist &l,int i,elemtype &e) {
if (!(p=getelemp_dul(l,i))) return error;
e=p->data;
p->prior->next=p->next;
p->next->prior=p->prior;
free(p);
return ok;
}
36、一个单链表,不懂得长度,写一个函数迅速找到中间节点的位置.
一般的措施就是先遍历得到链表的长度,然后再通过长度遍历得到链表的中间节点。愈加好的措施是:
1) 使用两个指针进行遍历,快指针每次步进2,慢指针每次步进1;
2) 当快指针抵达链表尾部的时候,慢指针指向的就是链表的中点。
(这个算法的思想和经典问题“判定链表中是否存在环”的思想是一致的)
函数代码如下:
node* find_mid_element(node* head)
{ if (NULL == head) return NULL;
if (head->_next == NULL) return head;
if (head->_next->_next == NULL) return head;
node* mid= head;
node* p = mid->_next;
while ((NULL != p) && (NULL != p->_next))
{ mid = mid->_next;
p = p->_next->_next;
}
return mid;
}
37、进程和线程的差异。
线程是指进程内的一个执行单元,也是进程内的可调度实体.
与进程的区分:
(1)调度:线程作为调度和分派的基本单位,进程作为拥有资源的基本单位
(2)并发性:不但进程之间能够并发执行,同一个进程的多个线程之间也可并发执行
(3)拥有资源:进程是拥有资源的独立单位,线程不拥有系统资源,但能够访问从属于进程的资源.
(4)系统开销:在创建或撤消进程时,因为系统都要为之分派和回收资源,导致系统的开销明显不小于创建或撤消线程时的开销。
38、Heap与stack的差异
Heap是堆,stack是栈。
Stack的空间由操作系统自动分派/释放,Heap上的空间手动分派/释放。
Stack空间有限,Heap是很大的自由存储区
C中的malloc函数分派的内存空间即在堆上,C++中对应的是new操作符。
程序在编译期对变量和函数分派内存都在栈上进行,且程序运行过程中函数调用时参数的传递也在栈上进行
39、存储过程是什么?有什么用?有什么优点?
存储过程(Stored Procedure)是一组为了完成特定功效的SQL 语句集,经编译后存储在数据库。中用户通过指定存储过程的名字并给出参数(假如该存储过程带有参数)来执行它。
存储过程用于实现频繁使用的查询、业务规则、被其他过程使用的公共例行程序
存储过程在创建时即在服务器上进行编译,因此执行起来比单个 SQL 语句快
40、In C++, what does "explicit" mean? what does "protected" mean?
c++中的explicit核心字用来修饰类的结构函数,表白该结构函数是显式的,在某些情况下,我们要求类的使用者必须显示调用类的结构函数时就需要使用explicit,反之默认类型转换也许会导致无法预期的问题。
protected控制的是一个函数对一个类的组员(包括组员变量及组员措施)的访问权限。protected组员只有该类的组员函数及其派生类的组员函数能够访问
41、在c++的一个类中申明一个static组员变量有无用?
在C++类的组员变量被申明为static(称为静态组员变量),意味着它为该类的所有实例所共享,也就是说当某个类的实例修改了该静态组员变量,也就是说无论创建多少对象,static修饰的变量只占有一块内存。其修改值为该类的其他所有实例所见;而类的静态组员函数也只能访问静态组员(变量或函数)。
static是加了访问控制的全局变量,不被继承。
42、解释一下多播(组播)和广播的含义?
组播:主机之间“一对一组”的通讯模式,也就是加入了同一个组的主机能够接收到此组内的所有数据,网络中的互换机和路由器只向有需求者复制并转发其所需数据。主机能够向路由器祈求加入或退出某个组,网络中的路由器和互换机有选择的复制并传输数据,即只将组内数据传输给那些加入组的主机。
广播:主机之间“一对所有”的通讯模式,网络对其中每一台主机发出的信号都进行无条件复制并转发,所有主机都能够接收到所有信息(无论你是否需要).
43、1).What is virtual function ?what is vtable used for?
虚函数重要用于实现多态用,基类的某个函数前加个Virtual 用来告诉编译系统,遇到这个处理过程时,要等到执行时再确定到底调用哪个类的处理过程;
每一个虚函数都会有一个入口地址,虚函数表保存所有虚函数的入口地址
2).What's the difference between "struct" and "class" in c++?
struct组员默认类型为public,class组员默认类型为private。即为数据的封装。
假如没有多态和虚拟继承,在C++中,struct和class的存取效率完全相同!简单的说就是,存取class的data member和非virtual function效率和struct完全相同!无论该data member是定义在基类还是派生类的。假如不是为了和C兼容,C++中就不会有struct核心字。
3).What do we need to make destructor vitual?why?
CObject的析构函数设为virtual型,则所有CObject类的派生类的析构函数都将自动变为virtual型,这确保了在任何情况下,不会出现因为析构函数未被调用而导致的内存泄露
4).What to declare member function as const?
void fun1(int a) const;const的作用是指在该函数内部不会变化此类的组员变量(除非该组员变量定义时加上violate核心字),否则修改了该组员变量就会报错.
44、C++中为何用模板类。
答:(1)可用来创建动态增加和减小的数据结构
(2)它是类型无关的,因此具备很高的可复用性。
(3)它在编译时而不是运行时检查数据类型,确保了类型安全
(4)它是平台无关的,可移植性
(5)可用于基本数据类型
45、Linux有内核级线程么。
答:线程一般被定义为一个进程中代码的不一样执行路线。从实现方式上划分,线程有两种类型:“用户级线程”和“内核级线程”。 用户线程指不需要内核支持而在用户程序中实现的线程,其不依赖于操作系统核心,应用进程利用线程库提供创建、同时、调度和管理线程的函数来控制用户线程。这种线程甚至在象 DOS 这么的操作系统中也可实现,但线程的调度需要用户程序完成,这有些类似 Windows 3.x 的协作式多任务。另外一个则需要内核的参加,由内核完成线程的调度。其依赖于操作系统核心,由内核的内部需求进行创建和撤消,这两种模型各有其好处和缺陷。用户线程不需要额外的内核开支,并且用户态线程的实现方式能够被定制或修改以适应特殊应用的要求,不过当一个线程因 I/O 而处在等候状态时,整个进程就会被调度程序切换为等候状态,其他线程得不到运行的机会;而内核线程则没有各个限制,有利于发挥多处理器的并发优势,但却占用了更多的系统开支。 Windows NT和OS/2支持内核线程。Linux 支持内核级的多线程
46、触发器怎么工作的?
答:触发器重要是通过事件进行触发而被执行的,当对某一表进行诸如UPDATE、 INSERT、 DELETE 这些操作时,数据库就会自动执行触发器所定义的SQL 语句,从而确保对数据的处理必须符合由这些SQL 语句所
展开阅读全文