【1】关键字类型题
常见的关键字有 sizeof、static、const、volatile
1、sizeof:通常与strlen做比较不同
例1:char str[] = “Hello” ;
char *p = str ;
int n = 10;
请计算
(1)sizeof (str ) = 6
(2)sizeof ( p ) = 4
(3)sizeof ( n ) = 4
(4)void Func ( char str[100])
{
…… ;
}
请计算sizeof( str ) = 4
(5)void * p = malloc( 100 );
请计算sizeof ( p ) = 4
例2:sizeof和strlen的区别
Sizeof是求数据类型所占空间的大小,是一个操作符,在编译时计算出结果
Strlen是求以\0结束的字符串的实际长度,是一个函数,在运行时才运算出结果,使用strlen求长度时需要进行初始化,所以没有初始化结果未知。
2、static:通常被问作用,以及在程序中有两处陷阱需要注意:函数中static修饰局部变量和static修饰全局变量。
例1:static有什么用途?(请至少说明两种)
1、Static定义的全局变量只能在本文件中使用,
2、加了static的局部变量会延长其生命周期,存在于整个程序的执行过程,但是其他函数无法使用(变量不可,地址可以)
3、Static函数只会在该模块中可见.
例2:在程序执行过程中,该程序的某一个函数func()中申请的static型变量V有以下哪些特性( BD)。× AB
A V仅能被func()使用
B V存在于整个程序执行过程
C V存在于func()被调用期间
D V能被整个程序使用
例2:下面的代码输出的结果是什么?为什么? 15
int counter(int i)
{
static int count = 0;
count = count + i;
return count;
}
int main(void)
{
int I,j;
for(i=0; i<=5; i++) j =coutner(i);
printf(“%d\n”,j);
return 0;
}
3、const:通常考察作用
例1:const 有什么用途?(请至少说明两种)
1、定义常量
2、修饰函数参数 返回值,被修饰的东西会被保护起来,不会被改变。
3、const 还可以用来修饰数组/数组 const char s[]="David";如果没有const,我们可能会在后面有意无意的写name[4]='x'这样的语句,
这样会导致对只读内存区域的赋值,然后程序会立刻异常终止。。
4、修饰全局变量,比如多任务机制中的共享资源
也许你可能会问,即使不用关键字 const,也还是能很容易写出功能正确的程序,那么我为什么还要如此看重关键字const呢?我也如下的几下理由:
1). 关键字const的作用是为给读你代码的人传达非常有用的信息,实际上,声明一个参数为常量是为了告诉了用户这个参数的应用目的。
如果你曾花很多时间清理其它人留下的垃圾,你就会很快学会感谢这点多余的信息。(当然,懂得用const的程序员很少会留下的垃圾让别人来清理的。)
2). 通过给优化器一些附加的信息,使用关键字const也许能产生更紧凑的代码。
3). 合理地使用关键字const可以使编译器很自然地保护那些不希望被改变的参数,防止其被无意的代码修改。简而言之,这样可以减少bug的出现。
例2:请问以下代码有什么问题:
char* s="AAA";
printf("%s",s)
s[0]='B';
printf("%s",s);
有什么错?
因为AAA 是字符串常量 编码时通常需要加const, const char* s="AAA";
因为AAA是常量 所以不能赋值
例3:const修饰指针
const(*号)左边放,我是指针变量指向常量;
const(*号)右边放,我是指针常量指向变量;
const(*号)两边放,我是指针常量指向常量;
4、volatile:通常考察作用和使用环境
例1:关键字volatile有什么含意? 并给出三个不同的例子。
volatile 修饰的变量是说明该变量的值会随时发生变化的,每次用的时候需要从存储该变量的地址中直接获取
1.中断服务程序中修改的供其他程序检测的变量需要加volatile
2.多任务环境下个任务间共享的标志位需要加volatile
3.存储器映射的硬件寄存器通常也要加volatile
例2:一个指针可以是volatile吗?
可以,因为指针和普通变量一样,有时也有变化程序的不可控性。
常见例:子中断服务子程序修改一个指向一个buffer的指针时,必须用volatile来修饰这个指针。
例3. 一个参数既可以是const还可以是volatile吗?解释为什么。
是的。一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。
例4. 下面的函数有什么错误:
int square(volatile int *ptr)
{
return *ptr * *ptr;
}
这段代码的有个恶作剧。这段代码的目的是用来返指针*ptr指向值的平方,但是,由于*ptr指向一个volatile型参数,编译器将产生类似下面的代码:
int square(volatile int *ptr)
{
int a,b;
a = *ptr;
b = *ptr;
return a * b;
}
由于*ptr的值可能被意想不到地该变,因此a和b可能是不同的。结果,这段代码可能返不是你所期望的平方值!正确的代码如下:
long square(volatile int *ptr)
{
int a;
a = *ptr;
return a * a;
}
5、extern:extern标识的变量或者函数声明其定义在别的文件中,提示编译器遇到此变量和函数时在其它模块中寻找其定义。
【2】#define 用法:考点:与typedef的区别和运算
例1:用宏定义写出swap(x,y),即交换两数。
#define swap(x,y) x=x+y;y=x-y;x=x-y;
例2:写一个“标准”宏,这个宏输入两个参数并返回较小的一个。
#define min(x,y) x>y?y:x
例3:设有如下的宏定义,则执行语句后的输出是(C)
#define f(x) (x*4)
printf("%d\n", 2 * f(5 + 3));
A 52 B 22 C 34 D 64
例4:typedef和define有什么区别
(1)用法不同:typedef用来定义一种数据类型的别名,增强程序的可读性。define主要用来定义常量,以及书写复杂使用频繁的宏。
(2)执行时间不同:typedef是编译过程的一部分,有类型检查的功能。define是宏定义,是预编译的部分,其发生在编译之前,只是简单的进行字符串的替换,不进行类型的检查。
(3)作用域不同:typedef有作用域限定。define不受作用域约束,只要是在define声明后的引用都是正确的。
(4)对指针的操作不同:typedef和define定义的指针时有很大的区别。
注意:typedef定义是语句,因为句尾要加上分号。而define不是语句,千万不能在句尾加分号;
#define dPS struct s *
typedef struct s * tPS;
比如:dps p1,p2 实质上就是 struct *p1 和struct p2
而 tps p1,p2 则是 struct *p1 struct *p2
【3】符号优先级
例1:下列运算的结果是多少? C
int a = 30 + 20 % 3 * 2;
A 4
B 42
C 34
D 64
复习运算符优先级表
【4】对地址的操作和强转问题
例1:要对绝对地址0x100000赋值,我们可以用(unsigned int*)0x100000 = 1234;
那么要是想让程序跳转到绝对地址是0x100000去执行,应该怎么做?
首先将其强转为函数指针 (void(*)())0x100000
再调用函数 *((void(*)())0x100000)() )
例2:嵌入式系统经常具有要求程序员去访问某特定的内存位置的特点。在某工程中,要求设置一绝对地址
为0x67a9的整型变量的值为0xaa66。编译器是一个纯粹的ANSI编译器。写代码去完成这一任务。
int *p;
p = (int *)0x67a9;
*p = 0xaa66;
例3:(void )ptr 和((void**))ptr 的结果是否相同?其中ptr为同一个指针。
相同
【5】位运算:
例1:对某一位清0 置1:
清零: x&=~( x<<1)
置1:x|=(x<<1)
例2:一语句实现x是否为2 的若干次幂的判断。
(x&(x-1))?1:0 是则打印0 不是则为1
例3:求一个数的二进制中的1的个数
int x = 9999;
int i = 0;
while(x) {
i++:
x = x&(x - 1);
}
return i;
【6】计算结构体大小:结构体的总大小是最大类型的整数倍;
例1:struct name1{
char str;
short x;
int num;
} ;求sizeof(name1) 8
例2:16、struct name2{
char str;
int num;
short x;
}; 求sizeof(name2) 12
【7】指针:指针数组、数组指针、野指针问题,内存越界问题,指针函数、函数指针
指针与数组问题
例1:下列表达式中a的值是多少? A
char test[8] = { 0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08 };
int *test_p = (int *)test;
int a = test_p[1];
A 0x08070605
B 0x04030201
C 0x05060708
D 0x01020304
例2:下来输出是什么? 0和1
char str1[] = "abc";
char str2[] = "abc";
const char *str5 = "abc";
const char *str6 = "abc";
cout << ( str1 = = str2 ) << endl;
cout << ( str5 = = str6 ) << endl;
str1和str2是两个不同的数组空间所以为0,str5和str6是指向同一块地址的指针 所以为1
例3:下列代码有什么问题
int main(void)
{
int **p; //P是指向指针变量的指针
int arr[100];
p = &arr;
return 0;
}
解答:
搞错了,是指针类型不同,
int **p; //二级指针
&arr; //arr代表了一个地址(一个具体的数值),而不是变量,所以不能对其取地址.
可改为:
int main(void)
{
int **p, *q;
int arr[100];
q = arr;
p = &q;
return 0;
}
例4:请写出以下代码的打印结果,主要目的是考察a和&a的区别。 2 和 5
#include
void main( void )
{
int a[5]={1,2,3,4,5};
int *ptr=(int *)(&a+1);
printf("%d,%d",*(a+1),*(ptr-1));
return;
}
(p+=2)[-1] ,也就是(p+2-1)
例5:用变量a 给出下面的定义
1) 一个有10个指针的数组,该指针是指向一个整型数的; int *a[10]
2) 一个指向有10个整型数数组的指针; int(*a)[10]
3) 一个指向函数的指针,该函数有一个整型参数并返回一个整型数; int (*a)(int)
4) 一个有10个指针的数组,该指针指向一个函数,该函数有一个整型参数并返回一个整型数;int (*a[10])(int)
例4:.若有定义int c[4][5],(*cp)[5];和语句cp=c,则能正确引用c数组元素的是 D
A cp+1 B.*(cp+3) C.*(cp+1)+3 D.*(*cp+2)
内存越界问题
例5:请问以下代码有什么问题:
int main()
{
char a;
char *str=&a;
strcpy(str,"hello");
printf(str);
return 0;
}
将字符串赋值给字符变量,内存不够,存在越界。
例6:下面的代码输出的结果是什么?
void test(void) {
char *p = null;
strcpy(p,"hello");
printf("%s\n",p);
}
段错误
野指针问题
例7:程序哪里有错误
wap( int* p1,int* p2 )
{
int *p;
*p = *p1;
*p1 = *p2;
*p2 = *p;
}
段错误,p没有空间,为其分配空间或者不使用指针
例8:简述指针常量与常量指针区别
指针常量强调的是指针的不可改变性,而常量指针强调的是指针对其所指对象的不可改变性。
例9:请问一下程序将输出什么结果?
char *RetMenory(void)
{
char p[] = “hellow world”;
return p;
}
void Test(void)
{
char *str = NULL;
str = RetMemory();
printf(str);
}
由于p[]是自动变量,所以RetMenory执行完毕时,资源被回收,p指向未知地址。因此str的内容应是不可预测的, 打印的应该是str的地址
void getmemery(char *p)
{
p = (char *)malloc(100);
}
main()
{
char *str = NULL;
getmemery(str);
strcpy(str,"hello world!");
printf("%s\n",str);
} 段错误
问题出在这,上述代码传入getmemery(char *p)函数的字符串指针是形参,在函数内部修改形参并不能真正的改变传入形参的值,
执行完char *str = NULL; gememory(str);后的str仍为NULL;
一般函数的传递都是值传递,不会改变函数外的变量值。简单地说,就是形参不能够改变实参,实参只是复制了一份给形参!其自身并没有被改变
void getmemery(char **p)
{
*p = (char *)malloc(100);
}
main()
{
char *str = NULL;
getmemery(&str);
strcpy(*str,"hello world!");
printf("%s\n",*str);
}
这就是我们常说的“地址传递”,将str的地址传给getmemery()函数,getmemery()函数就会通过地址修改str里面的值,这样就会得到正确的结果。
所以,我们要记住函数传参的两种方式:1)值传递 2)地址传递。
指针函数:本质是一个函数,返回一个指针。
函数指针:本质是一个指针,指向函数的起始地址
指针数组:是一个数组,数组中的元素是指针
数组指针:一个指针指向一个数组
【8】数据结构:考点:链表和数组的区别 堆
链表问题
例1:链表与数组的区别
(1)存储形式:数组是一块连续的空间,声明时就要确定长度。链表是一块可不连续的动态空间,长度可变,每个结点要保存相邻结点指针。
(2)数据查找:数组的线性查找速度快,查找操作直接使用偏移地址。链表需要按顺序检索结点,效率低。
(3)数据插入或删除:链表可以快速插入和删除结点,而数组则可能需要大量数据移动。
(4)越界问题:链表不存在越界问题,数组有越界问题。
例2:实现链表的翻转
void List_reverse(List_t L)
{
List_t p,q;
p = L->next;
L->next = NULL;
while(p != NULL) {
q = p;
p = p->next;
q->next = L->next;
L->next = q;
}
}
例3:实现链表的逆序输出
int List_nixu(List_t L)
{
if(L->next == NULL)
return 1;
List_nixu(L->next);
printf("%d\n",L->data);
}
例4:下列数据结构可以通过任意元素找到目标元素的结构是(B)。
A 顺序链表
B 双链表
C 单链表
D 静态链表
例5:查找(删除)重复元素
1、数组a[N],存放了1至N-1个数,其中某个数重复一次。写一个函数,找出被重复的数字.时间复杂度必须为o(N)函数原型:
int do_dup(int a[],int N)
int do_dup(int arr[],int NUM)
{
int temp=0;
for(int i=0; i=NUM)
temp=arr[i]-NUM; // 该值重复了,因为曾经加过一次了
else
temp=arr[i];
if(arr[temp]
2、链表删除重复节点
void List_order_del(List_t L)
{
List_t p,q,t;
p = L->next;
while(p != NULL) {
q = p->next;
while(q != NULL) {
if(p->data == q->data) {
q->data = q->next->data;
t = q->next;
q = q->next;
q->next = t->next;
free(t);
t = NULL;
}
else {
q = q->next;
}
}
p = p->next;
}
}
堆排序问题:堆排序:大根堆:每个根节点大于孩子节点,小根堆:每个根节点小于孩子节点,
实质:二叉树
例6:优先级队列使用什么数据结构实现?
二叉堆
例7:层序遍历的基本思路是:从二叉树的根节点开始,层数依次从1到n层,每层都从左儿子到右儿子遍历,
依次按照层顺序遍历。下列序列都是经过层序遍历完全二叉树的结果,其中是堆的序列是(B )。
A 94、21、36、28、57、73
B 21、28、57、36、94、73
C 21、36、73、28、57、94
D 94、57、36、28、73、21
**快慢指针:**查找到链表中间位置,判断循环链表。
得到链表最后k个元素:定义两个指针,A向前移动k步,一B指向头,然后同时移动,直到A=NULL。
【9】排序算法
例1:冒泡排序:一次比较两个元素,如果他们的顺序(如从大到小)把他们交换过来。把最大或最小的放到边上
for(i=0;i
例2:选择排序:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
for(i=0;ia[x])
x = j;
}
if(x != i) {
tmp = a[x];
a[x] = a[i];
a[i] = tmp;
}
}
例3: 插入排序:对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。
for(i=1;i0&&a[j-1]>tmp;j--)
a[j] = a[j-1];
a[j]=tmp;
}
【10】查找算法
例1:二分法查找
while(1) {
i = (max + min)/ 2 + 1;
if(x == a[i]) {
printf("%d\n",i);
break;
}
else if(x > a[i])
min = i;
else
max = i;
}
【11】内存分配
只读段:具有只读属性,包含程序代码(.init和.text)和只读数据(.rodata);
数据段:data段,存放的是全局变量和静态变量,其中初始化数据段存放在显示初始化的全局变量和静态变量,
未初始化数据段:此段通常被称为BBS段,存放未进行显示初始化的全局变量和静态变量。
栈stack:由系统自动分配释放,存放函数的参数值、局部变量的值、返回地址等。栈向低地址扩展的数据结构,大小有限制2M/1M,申请地址空间连续,申请效率高
堆Heap存放动态分配的数据:一般由程序员动态分配和释放,若程序员不释放,程序结束可能由系统自动回收。申请空间和虚拟内存有关比较大,申请内存不连续,效率低
共享库的内存映射区域:这是Linux动态链接器和其他共享库代码的映射区域
缓冲区溢出问题
例1:堆栈溢出一般是由什么原因导致的?
存入数据太多,没有及时的回收资源,导致内存不足
例2:什么是缓冲区溢出?有什么危害?其原因是什么?
缓冲区溢出是指当计算机向缓冲区内填充数据位数时超过了缓冲区本身的容量溢的数据覆盖在合法数据上, 会修改其他内存中的数据。
数据存储问题
例3:下面的C程序在Linux下使用gcc编译成功后变量k处于(B)中。
int j = 100;
int main(int argc, char **argv)
{
static k = 0;
int i = 0;
for (i = 0; i
malloc问题:跨函数使用malloc
例4: 请问运行Test 函数会有什么样的结果 段错误
void GetMemory(char *p) 修改为 GetMemory(char **p)
{
p = (char *)malloc(100);
}
void Test(void)
{
char *str = NULL;
GetMemory(str); 修改为 GetMemory(&str);
strcpy(str, "hello world");
printf(str);
}
传入一个一级指针,只能修改它指向的数据,而不能修改它指向的地址。所以我们应该传入一个二级指针,
这个指针指向一级指针。这样我们就能修改位于二级指针指向的数据,即一级指针指向的地址了。
【12】死循环
1、while(1) { }
2、for(;;) { }
3、Loop:
...
goto Loop;
【13】常见编程题
实现各种str函数
例1:实现 strcpy函数
char cpy(char *dest,char *src)
{
int i = 0;
while(src[i] != '\0') {
dest[i] = src[i];
i++;
}
dest[i]='\0'
}
例2:实现 strcmp函数
int cmp(const char *dest,const char *src)
{
int flag = 0;
while(*dest !='\0'&&*src !='\0') {
if(*dest == *src){
flag = 0;
}
if(*dest>*src){
flag = 1;
break;
}
if(*dest<*src){
flag = -1;
break;
}
*dest++;
*src++;
}
return flag;
}
例3:实现strlen函数
int mystrlen(char *s1)
{
int i = 0;
while(*s1 != '\0') {
s1++;
i++;
}
return i;
}
例4:实现strcat函数
void mystrcat(char *s1,char *s2)
{
while(*s1 != '\0') {
s1++;
}
while(*s2 != '\0'){
*s1++ = *s2++;
}
*s1 = '\0';
}
例3:打印杨辉三角
for(i=1;i<10;i++) {
a[i][0] = 1;
printf("%5d",a[i][0]);
for(j=1;j
例4:请使用递归算法编写求N的阶乘函数。
double factorial(unsigned int i)
{
if(i <= 1)
{
return 1;
}
return i * factorial(i - 1);
}
例5:斐波那契数列
int fun(int x)
{
int sum = 0;
if(x==0)
return 0;
if(x==1)
return 1;
sum=fun(x-1)+fun(x-2);
return sum;
}
int main()
{
int x=10;
int i;
for(i=0;i
【14】函数传参
例1:
int sub(int a,int b)
{
return a-b;
}
int main(int argc,char **argv)
{
int i=2;
int y=sub(++i,++i);
printf("%d",y);
return 0;
答案:0 函数传递参数前,先把参数计算好,再一次性把所有参数入栈
}
【15】大端序和小端序
**小端序(**little-endian) - 低序字节存储在低地址 符号位为最高地址
将低字节存储在起始地址,称为“Little-Endian”字节序,Intel、AMD、X86等采用的是这种方式;
大端序(big-endian)- 高序字节存储在低地址
将高字节存储在起始地址,称为“Big-Endian”字节序,由ARM、Motorola等所采用
内存地址 小端 大端
(低) 0x0001 0x78 0x12
0x0002 0x56 0x34
0x0003 0x34 0x56
(高) 0x0004 0x12 0x78
例1:通过C语音来判断处理器大端序和小端序,小端序返回1;大端序返回0
int fun()
{
int n = 1;
char ch = (char)n;
if(ch)
return 1;
else
return 0
}
【16】 a++ 和 ++a
a++:先用后加;
++a:先加后用
【17】gdb调试
【18】 文件中内容中间段替换
临时文件
【19】cpu占用率高问题。
【20】内存管理机制
分页管理