#pragma comment。将一个注释记录放置到对象文件或可执行文件中。
#pragma pack。用来改变编译器的字节对齐方式。
#pragma code_seg。它能够设置程序中的函数在obj文件中所在的代码段。如果未指定参数,函数将放置在默认代码段.text中
#pragma once。保证所在文件只会被包含一次,它是基于磁盘文件的,而#ifndef则是基于宏的。当类不包含任何成员的时候,大小本该是0,但是为了便于区分,大小是1
.c是标准C程序文件名的后缀;.cpp则是C++程序文件名的后缀;.obj是源程序经编译后所生成的目标文件的扩展名;.exe则是源程序经编译、链接后所生成的执行文件的扩展名。
结构体变量不管其包含有多少个成员,都应当看成是一个整体。在程序运行期间,只要在变量的生存期内,所有成员一直驻留在内存中
C提供的三种预处理功能:宏定义 文件包含 条件编译
在C语言程序中,若对函数类型未加显式说明,则函数的隐含类型为int,C++中为void
const 限定一个数据为只读属性。
(1)const char p
; 限定变量 p 为只读。
(2)const char *p
; p 为一个指向 char 类型的指针,const 限定 p 指向的数据为只读。所以 *p 的值不能被修改,而指针变量 p 本身的值可以被修改。
(3)char * const p
; 限定此指针变量为只读,所以p的值不能被修改,而 *p 的值可以被修改。
(4)const char *const p
; 者皆限定为只读,不能修改。一个指向指针的指针,它指向的指针是指向一个整型数
int **a;
一个有10个指针的数组,该指针是指向一个整型数的int *a[10];
一个指向有10个整型数数组的指针int (*a)[10];
gcc和g++的主要区别
(1)对于 .c和.cpp文件,gcc分别当做c和cpp文件编译(c和cpp的语法强度是不一样的)
(2)对于 .c和.cpp文件,g++则统一当做cpp文件编译
(3)使用g++编译文件时,g++会自动链接标准库STL,而gcc不会自动链接STL
(4) gcc在编译C文件时,可使用的预定义宏是比较少的
(5)gcc在编译cpp文件时/g++在编译c文件和cpp文件时(这时候gcc和g++调用的都是cpp文件的编译器),会加入一些额外的宏ftell() 函数用于得到文件位置指针当前位置相对于文件首的偏移字节数;
fseek() 函数用于设置文件指针的位置;
rewind() 函数用于将文件内部的位置指针重新指向一个流(数据流/文件)的开头;
ferror() 函数可以用于检查调用输入输出函数时出现的错误。结构体的内存分配
结构体在内存中分配一块连续的内存,但结构体内的变量并不一定是连续存放的,这涉及到内存对齐。
原则1 数据成员对齐规则: 结构(struct或联合union)的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员存储的起始位置要从该成员大小的整数倍开始(比如int在32位机为4字节,则要从4的整数倍地址开始存储)。
原则2 结构体作为成员: 如果一个结构里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储。(struct a里存有struct b,b里有char,int,double等元素,那b应该从8的整数倍开始存储。)
原则3 收尾工作: 结构体的总大小,也就是sizeof的结果,必须是其内部最大成员的整数倍,不足的要补齐。不同系统下数据类型的字节大小
int (*(*p)[10])(int *)
先看未定义标识符p
,p
的左边是*
,*p
表示一个指针,跳出括号,由于[ ]
的结合性大于*
,所以*p
指向一个大小为10的数组,即(*p)[10]
。左边又有一个*
号,修释数组的元素,*(*p)[10]
表示*p
指向一个大小为10的数组,且每个数组的元素为一个指针。跳出括号,根据右边(int *)
可以判断(*(*p)[10])
是一个函数指针,该函数的参数是int*
,返回值是int
。结构体和联合的区别
(1) 联合和结构体都是由多个不同的数据类型成员组成,但在任何同一时刻,联合只存放了一个被选中的成员,而结构体的所有成员都存在。
(2) 对于联合的不同成员赋值,将会对其它成员重写,原来成员的值就不存在了,而对于结构体的不同成员赋值是互不影响的。
结构体中各成员都有自己的内存空间,是同时共存的,长度是所有成员的和。考虑内存对齐。
联合体中所有的成员只有一个内存空间,不同时刻储存不同类型的值,长度是它最长成员的长度。fopen(fname,"w") 函数的打开方式
"r"
以“只读”方式打开文件。只允许读取,不允许写入。文件必须存在,否则打开失败。
"w"
以“写入”方式打开文件。如果文件不存在,那么创建一个新文件;如果文件存在,那么清空文件内容(相当于删除原文件,再创建一个新文件)。
"a"
以“追加”方式打开文件。如果文件不存在,那么创建一个新文件;如果文件存在,那么将写入的数据追加到文件的末尾(文件原有的内容保留)。
"r+"
以“读写”方式打开文件。既可以读取也可以写入,也就是随意更新文件。文件必须存在,否则打开失败。
"w+"
以“写入/更新”方式打开文件,相当于w和r+叠加的效果。既可以读取也可以写入,也就是随意更新文件。如果文件不存在,那么创建一个新文件;如果文件存在,那么清空文件内容(相当于删除原文件,再创建一个新文件)。
"a+"
以“追加/更新”方式打开文件,相当于a和r+叠加的效果。既可以读取也可以写入,也就是随意更新文件。如果文件不存在,那么创建一个新文件;如果文件存在,那么将写入的数据追加到文件的末尾(文件原有的内容保留)。#include 用来包含开发环境提供的库头文件,
#include”filename.h” 用来包含自己编写的头文件关键字 inline 只是一种编译器建议,inline编译阶段嵌入,运行时不算调用,因此不需要栈
BSS段:存放程序中未初始化的全局变量的一块内存区域,BSS段属于静态内存分配。
数据段:存放程序中已初始化的全局变量的一块内存区域。数据段属于静态内存分配。
代码段:存放程序执行代码的一块内存区域。
堆(heap):存放进程运行中被动态分配的内存段,内存手动申请手动释放
栈(stack):栈又称堆栈, 是用户存放程序临时创建的局部变量,也就是说我们函数括弧“{}”中定义的变量(但不包括static声明的变量,static意味着在数据段中存放变量)。内存自动申请自动释放Char *const *(*next)();
请对这一行代码进行一下解释?
Next是一个函数指针,指向一个没有参数的函数,并且该函数的返回值是一个指针,该指针指向一个类型为char的指针常量。Char *(*c[10])(int **p);
*c[10]
为一个指针数组,其数组中的每一个元素都是函数指针,所指向的函数返回值是char*
类型,且函数参数为int **p
是一个指向指针的指针请问一下代码是否有问题,如果有问题请指出,没问题运行结果是什么?
Char *pt=”AAA”; Printf(“%s”,pt); //输出为 AAA*pt=’B’;Printf(“%s”,pt); //出错 (段错,核心已转存)pt=”BBB”;Printf(“%s”,pt); //输出为 BBB
答”AAA”为字符串常量,编译后放置在内存区的只读区域,只读区域不允许修改,指针指向地址的内容不允许修改,但指针的指向可以修改。
- 在某工程中,要求设置一绝对内存地址为0x40020800的位置,将该地址里面的内容设置为整型值0x3456。编写代码完成这一任务。
以下提供三种方法:
① int *pt;pt=(unsigned long *)0x40020800; *pt=0x3456;② *(unsigned long *)0x40020800=0x3456;③ #define ADDR (*(volatile unsigned long *)0x40020800) ADDR=0x3456;
- typedef在C语言中频繁用以声明一个已经存在的数据类型的同义字。也可以用预处理器做类似的事。例如,思考一下下面的例子:
#define dPS (struct s *)typedef (struct s *) tPS;
以上两种情况的意图都是要定义dPS和tPS作为一个指向结构s指针。哪种方法更好呢” />
- 取出当前计算机系统下无符号整数int类型的0值和其最大值
unsigned int zero = 0;unsigned int compzero= ~0;
- printf()函数的返回值为打印字符的个数
int i = 43;printf ("%d \n", printf ("%d",printf("%d",i))) ; //输出为4321
- 举例说明,通过 #运算符,利用宏参数创建字符串
①
#define SQUARE(x) (printf("x square is : %d\n",(×)*(x))) SQUARE(4); //输出为: x square is:16
②
#define SQUARE(x) (printf(""#x" square is : %d\n",(×)*(x)))SQUARE(4); //输出为: 4 square is:16
“#”可以将普通文本替换成语言符号
- 举例说明,“##”运算符的作用。
预处理的粘合剂,用它把两个语言符号合并成个语言符号
#define XNAME(n) x##n
则有 int x1=10 等价于 int XNAME(1)=10
- 结构体变量中使用字符数组优于字符指针
struct std{unsigned int id;char *name; unsigned int age;}per;strcpy(per.name,"Micheal Jackson"); //报错,name未初始化且未申请内存,野指针 存在一定风险
- C和C++中,
free()
和delete
是如何操作指向动态开辟内存的指针的?
// char *p=(char *)malloc(100); //c语言char *p=new char[100]; //c++Strcpy(p,”ABCDEF”);cout<<”p=”<<p; //输出 ABCDEF// free(p);delete []p;If(p!=0){ //c++中可用0来表示空指针Strcpy(p,”hello world”);cout<<”p=”<<p; //输出 hello world}
所以指针一般释放完后不再使用,而是置空 free(p); p=NULL;
free和delete:将该指针所指向的内存给释放掉,但是并没有把指针本身给消除掉
#define offsetof(TYPE,MEMBER) ((size_t)&((TYPE *)0)->MEMBER)
请尝试解释下上面这行语句的含义。
1)(TYPE*)0
:将0强制类型转换为TPYE类型的指针,p = (TYPE*)0
2)((TYPE *)0)->MEMBER
—-> p->MEMBER—>访问MEMBER成员变量
3)&((TYPE *)0)->MEMBER
: 取出MEMBER成员变量的地址
4)(size t)&((TYPE*)))->MEMBER
: size_t相当于类型转换,将MEMBER成员变量的地址转换为size_t类型的数据,size_t== int
总结:该宏的作用就是求出MEMBER成员变量在TYPE中的偏移量
- const关键字作用:
(1)const定义一个常量,这个常量在定义时必须进行初始化,否则以后就没有机会了
(2)实际上const定义的变量并不是一个真正的常量(指针,数组两种方式验证这个观点)
const int i=10;// int a[i]; 会报错,因为i并没有成为真正意义上的常量int *pt=&i;*pt=20;printf(“i=%d”,i); //输出20,即可证明i并不是真正的常量,本质依旧是变量
(3)const和指针的用法
const int *pt; //两者相同,都代表pt指针可以指向任意对象,但是不能通过pt指针来修改指向对象的值。int const *pt; Int *const pt; //可以修改指向对象的值,不可以修改为指向其他对象
(4)const修饰形参
Fork()函数 下面这段代码共创建几个进程
int main()
{
fork()||fork();
}
一共会创建3个子进程,左边先第一个fork()函数时,父进程返回子进程pid号,子进程返回0,所以父进程不再去运行右边的fork()函数,而子进程则会去运行右边的fork()函数进而生成一个孙子进程,所以一个有3个进程若MyClass为一个类,执行“
MyClass a[4],*p[5];
”语句时会自动调用该类构造函数的次数是?
答:4次,构造函数是每创建一个类的对象调用一次,则a[4]
调用4次,而*p[5]
为指向对象的指针 不会调用构造函数统计二进制数中1的个数 例:求下面函数的返回值
int func (int x) {int countx = 0;while(x){ countx++;x=x& (x-1); }return countx;}
答:
x-1
: 将二进制数从右往左数,遇到的第一个1变成0,右边的所有0变成1,左边的所有数保持不变
200 ---> 11001000 200-1 ----> 11000111 200&200-1 ----> 11001000 & 11000111 --->11000000
x&(x-1)
–>从右往左数,遇到的第一个1变成0,左边的数不变,右边的数全部为0 即将二进制数从右往左数,遇到的第一个1变成0。
- 判断一个数是否为2的n次方
2--->10 4--->100 8--->1000 16--->10000 32--->100000 ....
所以也可以用37题那样用x&x-1进行判断,如果结果等于0 则为2的n次方
- 变量置位和清零操作 给定一个整型变量a,写两段代码,第一个设置a的bit3,第二个清除a的bit3。
答:
只要出现置1:或操作
只要出现清0:与操作
移位操作:左移:<>
a第三位置1 : a|=(1<<3)a第三位置0: a&=~(1<<3)
- sizeof运算符中的表达式 请说明下面代码的运行结果:
#include int main (void){int i ;i = 10;printf ("%d\n", i); //输出10printf ("%1d\n", sizeof(i++)) ; //输出4 (int占4字节)printf ("%d\n", i); //③输出10return 0;}
③sizeof不是函数,sizeof是运算符
sizeof不会计算里面表达式的值,只判断表达式结果是什么类型就可以(即sizeof(i++)不会运行i++这个表达式)
- 指针+整数 请说明下面代码的运行结果:
unsigned char *p1 ;unsigned long *p2;pl = (unsigned char *)0x801000;p2 = (unsigend long *)0x801000;
请问:p1 + 5 = ?p2 + 5 = ?
答:
指针+1:指针+1不是地址加1,所增加的地址值为这个指针指向的类型所占用的内存大小,指针+1 ==地址+ sizeof(指向数据的类型),且 sizeof(指向数据的类型)为十进制需转换为16进制再相加
p1 + 1 = 0x801000 + sizeof(unsigned char)=0x801000 + 1 = 0x801000p2 +1 = 0x801000 + sizeof (unsigned long)=0x801000 + 4 = 0x801004p1 + 5 = 0x801000 + sizeof(unsigned char)*5= 0x801005 p2 + 5 = 0x801000 + sizeof (unsigned long)*5= 0x801000 + 4 *5 = 0x801014 //(20=0x16)
- volatile关键字 下面的函数有什么错误:
int square(volatile int *pt){return (*pt)*(*pt) ; }
答:
编译器会对这段代码进行如下处理;
int square(volatile int *pt){int a,b;a= *pt; //每使用一次*pt编译器都会重新定义一个数指定它 即可能出现b = *pt; //当a指向*pt=4时,b再指向*pt 就可能变成5了,*pt可能发生变化return a * b;}
正确写法:
Int square(volatile int *pt){int a; a=*pt; return a*a; }
- 如果a为数组,则a和&a的区别
int main(){int a[5]={1,2,3,4,5}; int *pt=(int *)(&a+1); //&a是指向整个数组的地址,*pt是指向单个元素的printf(“%d,%d”,*(a+1),*(pt-1)); //输出 2 和 5 地址,所以需强制转换return 0; }
(1) a==&a
a+1!=&a+1
a
:数组名,表示数组中第一个元素的地址,指向的是数组中元素的地址
&a
:整个数组再内存中的起始地址,指向的是整个数组作为一个单元的起始地址
- 不使用第三个变量的情况下 交换俩个变量的值
a=a+b; b=a-b; a=a-b;
- 运算符优先级
cout<<(5>6)" />; //输出10 20 30 cout<<endl;return 0;}
- 用C代码查看一个文件大小
#include#includeint main(){FILE *fp;long int size=0;fp=fopen("test.txt","r"); //以只读的方式打开当前目录下test.txt文件fseek(fp,0,SEEK_END); //将文件位置指针指向文件末尾size=ftell(fp); //获取文件位置指针相对于文件首的偏移字节数fclose(fp);printf("The size of file is:%ld\n",size);return 0; }
- 逗号表达式 下列语句的运算结果是:
int main(){int x=10,y=3,z;printf(“%d\n”,z=(x%y,x/y));return 0;}
逗号表达式的一般形式:(表达式1,表达式2,表达式3…表达式n) —->整个表达式的值为表达式n,即上面z=(1,3); 所以z=3;
- 成员初始化列表(只限于构造函数和非静态const数据成员)
①如果Classy是一个类,而mem1、mem2和mem3都是这个类的数据成员,则类构造函数可以使用如下的语法来初始化数据成员:
Classy::Classy(int n,int m):mem1(n),mem2(0),mem3(n*m+2){ }
上述代码将mem1初始化为n,将mem2初始化为0,将mem3初始化为n*m+2。
②派生类(子类)构造函数可以使用初始化器列表机制将值传递给基类(父类)构造函数
创建派生类对象时,程序首先调用基类构造函数,然后再调用派生类构造函数,先调用派生类的析构函数,再调用基类的析构函数
#includeusing namespace std;class BASE{char c;public:BASE(char n):c(n) // 类的成员初始化列表的方法 {}virtual ~BASE() //基类里要有一个虚析构函数 {cout<<c; //输出Y}};class DERIVED:public BASE //继承BASE {char c;public:DERIVED(char n):BASE(n+1),c(n) //派生类(子类)构造函数可以使用初始化器列表机制将值传递给基类(父类)构造函数{}~DERIVED(){cout<<c; //输出X}};int main(){DERIVED('X'); //匿名的DERIVED类对象 先调用基类构造函数,再调用派生类构造函数,先调用派生类的析构函数,再调用基类的析构函数return 0;}
输出为 XY
运算符优先级和结合律
浮点数与位操作 下列表达式式中,()是合法的。已知double m=3.2,int n=3;
A.!m*=n B. (m+n)|n C. m=5,n=3.1,m+n D. m<<2
答案选C
A: !优先级高于*=,所以 !m*=n —> flase*=n —> 0*=3 0不能再赋值,所以错误
B: (m+n)|n —> 6.2|3 由于”|”位操作运算符 俩侧都必须是整数,所以错误
C: m=5.0,n=3, 则m+n=5.0+3.0=8.0 正确
D: m< 3.2<<2 “<<”位操作运算符 运算符俩侧都必须为整数,所以错误new运算符 假定p是具有
int **
类型的指针变量,则给p赋值的正确语句为()
A、p=new int;
B、p=new int[10];
C、p=new int*;
D、p=new int**;
答案选C
A:new int;
开辟一段内存空间,里面存放的是整数,new运算符返回的是地址 用指针接受,即定义一个指向证书的指针。int *p=new int;
B:new int[10];
开辟一段内存空间,里面存放的是数组,new int[10]返回这段内存空间的首地址,即定义一个指向数组的指针。int arr[10]={0};
int *p=arr;
—>int *p=new int[10];
C:new int*;
开辟一段内存空间,里面存放的是指针,则需要定义一个指向指针的指针作为new int*
的返回值。int **p=new int*;
D:int ***p=new int**;
有符号数和无符号数 下面程序的输出结果是()
int main(){char x=0xFF;printf(“%d\n”,x--);return 0;}A、-1 B、0 C、255 D、256
答案选A
char x=0xFF —> 1111 1111 —>%d的格式转换显示 十进制+有符号
有符号的最高位为1 那么说明这个数一定是负数
有符号数在内存当中是以补码的形式存储的 ,所以补码(反码+1)为 1111 1111,即可求出反码为 1111 1110,原码=反码取反(符号位不变):1000 0001 —-> 原码=-1
请问在C语言中,关键字static有哪三个明显的作用” />
- C语言如何面向对象
多态
#include "stdio.h"typedef struct Person{ char *name; void (*eat)();}Person;void fun1(){ printf("student eat!\r\n");}void fun2(){ printf("teacher eat!\r\n");}int main(){ Person student={"student",fun1}; Person teacher={"teacher",fun2}; student.eat(); teacher.eat();}
线程和进程的区别
① 一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。线程依赖于进程而存在。
②进程在执行过程中拥有独立的内存单元,而多个线程共享进程的内存。(资源分配给进程,同一进程的所有线程共享该进程的所有资源。同一进程中的多个线程共享代码段(代码和常量),数据段(全局变量和静态变量),扩展段(堆存储)。但是每个线程拥有自己的栈段,栈段又叫运行时段,用来存放所有局部变量和临时变量。)
③进程是资源分配的最小单位,线程是CPU调度的最小单位;
④系统开销: 进程切换的开销也远大于线程切换的开销。
⑤通信:由于同一进程中的多个线程具有相同的地址空间,致使它们之间的同步和通信的实现,也变得比较容易。进程间通信IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信——需要进程同步和互斥手段的辅助,以保证数据的一致性。
⑥进程间不会相互影响 ;线程一个线程挂掉将导致整个进程挂掉实现strcpy和swap
#include "stdio.h"void mstrcpy(char * dest,const char * src){ while(*src!='\0'){ *dest++=*src++; }}void swap(int *a,int *b){ *a=*a+*b;// a=11 *b=*a-*b;// b=11-6=5 *a=*a-*b;// a=11-5=6}int main(){ char buf[20]={0}; mstrcpy(buf,"123123123123"); printf("\r\n%s\r\n",buf); int a=10; int b=20; swap(&a,&b); printf("\r\n%d,%d\r\n",a,b); return 0;}
请问什么是预编译,何时需要预编译?
总是使用不经常改动的大型代码体;程序由多个模块组成,所有模块都使用一组标准的包含文件和相同的编译选项。在这种情况下,可以将所有包含文件预编译为一个预编译头。
预编译指令指示了在程序正式编译前就由编译器进行的操作,可以放在程序中的任何位置。请问局部变量能否和全局变量重名
能,局部会屏蔽全局。
局部变量可以与全局变量同名,在函数内引用这个变量时,会用到同名的局部变量,而不会用到全局变量请问引用与指针有什么区别?
1)引用必须被初始化,指针不必。
2)引用初始化以后不能被改变,指针可以改变所指的对象。
3)不存在指向空值的引用,但是存在指向空值的指针。请问以下代码有什么问题:
int main(){char a;char *str=&a;strcpy(str,"hello");printf(str);return 0;}
没有为str分配内存空间,将会发生异常,问题出在将一个字符串复制进一个字符变量指针所指地址。虽然可以正确输出结果,但因为越界进行内在读写而导致程序崩溃。
- 联合体和结构体占内存大小 观察以下代码在64位系统下分别输出多少?
#include using namespace std;typedef union { long i; char k[13]; char c; } DATE; struct data { int cat; DATE cow; double dog; } too; int main(){DATE max; cout<<sizeof(struct data)<<endl<<sizeof(max); return 0;}
输出为
32 16
①联合体中,所有成员共用一块内存,在64为系统下long为8字节,即为8字节对齐,又char k[13]占13字节,所以sizeof(max)=16;
②结构体中,各成员各自占有一块内存,同时共存,在上述结构体中,由于DATE为8字节对齐,所以int car占8字节,其次DATE cow占16字节,最后double dog占8字节,一起占32字节,即sizeof(struct data)=32;- 预处理器
①用预处理指令#define 声明一个常数,用以表明1年中有多少秒(忽略闰年问题)
#define SECONDS_PER_YEAR (60 * 60 * 24 * 365)UL //UL表示无符号长整形
②已知一个数组table,用一个宏定义,求出数据的元素个数。
#define NTBL (sizeof(table) / sizeof(table[0]))
③写一个”标准”宏MIN ,这个宏输入两个参数并返回较小的一个。
#define MIN(A,B) ( (A) <= (B) ? (A) : (B))
- 数据声明
用变量a给出下面的定义
a) 一个整型数
b)一个指向整型数的指针
c)一个指向指针的的指针,它指向的指针是指向一个整型数
d)一个有10个整型数的数组
e) 一个有10个指针的数组,该指针是指向一个整型数的
f) 一个指向有10个整型数数组的指针
g) 一个指向函数的指针,该函数有一个整型参数并返回一个整型数
h) 一个有10个指针的数组,该指针指向一个函数,该函数有一个整型参数并返回一个整型数
a) int a; b) int *a; c) int **a; d) int a[10]; e) int *a[10]; f) int (*a)[10]; g) int (*a)(int); h) int (*a[10])(int);
- 将一个链表逆置 如abcd —> dcba
NODE *fun(NODE *h){ NODE *p, *q, *r; p=h; if(p==NULL){ return NULL; } q=p->next; p->next=NULL; while(q){ r=q->next; q->next=p; p=q; q=r; } return p;}
设数组data[m]作为循环队列的存储空间。front为队头指针,rear为队尾指针,则执行出队操作后其头指针front值为(D)
A.front=front+1
B.front=(front+1)%(m-1)
C.front=(front-1)%m
D.front=(front+1)%m
解析:循环队列中出队操作后头指针需在循环意义下加1,因此为front=(front+l)%m死锁的四个条件及处理方法
(1)互斥条件:一个资源每次只能被一个进程使用。
(2) 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
(3)不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
(4)循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。解决死锁的方法分为死锁的预防,避免,检测与恢复三种
写出两个排序算法,并说明哪个好?
答:一般使用冒泡法和快速排序法,对堆栈局域比较小的单片机来说冒泡法比较好,对时间要求苛刻的实时响应来说快速排序法好。
题注:时间复杂度:一个算法花费的时间与算法中语句的执行次数成正比例,用T(n)表示,n为问题的规模,若有某个辅助函数f(n),使得当n趋近于无穷大时,T(n)/f(n)的极限值为不等于零的常数,则称f(n)是T(n)的同数量级函数。记作T(n)=O(f(n)),称O(f(n)) 为算法的渐进时间复杂度,简称时间复杂度。时间频度不同,但时间复杂度可能相同。如:T(n)=n2+3n+4与T(n)=4n2+2n+1它们的频度不同,但时间复杂度相同,都为O(n2)。
本题中,最坏的情况要计算9+8+7+6+5+4+3+2+1次,即n的级数,结果为n*n/2=(n2)/2。所以冒泡法时间复杂度O(n2)
1) 冒泡法:从小到大排,时间复杂度O(n2)#define MAX_NUM 10int a_data[MAX_NUM]={1, 2, 3, 4, 5, 6, 7, 8, 9, 0}; // 也可以用scanf获取int main(void){ int i=0, j=0, tmp=0; for(i=0; i<MAX_NUM-1; i++) { for(j=i+1; j<MAX_NUM; j++) { if(a_data[i] > a_data[j]) { // 如果i不是小于j,则调换 tmp = a_data[i]; a_data[i] = a_data[j]; a_data[j] = tmp; } } } return 0;}
2)交换排序-快速排序,类似于二分法:采用递归。选第一个点或中间的点,左边放最小值,右边放最大值,依次递归到最低层的两个元素。因为每递归一次要压栈一次,占用存储空间,所以空间复杂度较高。时间复杂度O(nlog2n)
#define MAX_NUM 16int a_data[MAX_NUM]={1, 2, 3, 4, 5, 6, 7, 8, 9, 0,11,12,13,14,15,16}; // 也可以用scanf获取//快速排序 void quick_sort(int s[], int l, int r){if (l < r){//Swap(s[l], s[(l + r) / 2]); // 将中间的这个数和第一个数交换 可换可不换 int i = l, j = r, x = s[l]; // s[l]会先被替换,最后这个值会被写回到s[i],它们也充当了临时变量的角色while (i < j){while (i < j && s[j] >= x) // 从右向左找第一个小于x的数 j--;if (i < j)s[i++] = s[j];while (i < j && s[i] < x) // 从左向右找第一个大于等于x的数 i++;if (i < j)s[j--] = s[i];}s[i] = x;quick_sort(s, l, i - 1); // 递归调用 quick_sort(s, i + 1, r);}}int main(void){ sort(a_data, 0, MAX_NUM - 1);}