2024秋招C语言嵌入式笔试面试题

内容来自网络,侵权联系删除

1、float x 与“零值”比较的 if 语句

   const float EPSINON = 0.00001;
   if ((x >= - EPSINON) && (x <= EPSINON)
注:不可将浮点变量用“==” 或“!=” 与数字比较,应该设法转化成“>=” 或“<=” 此类形式。

2、bool flag 与“零值”比较的 if 语句

   if ( flag ) or if ( !flag )

3、char *p 与“零值”比较的 if 语句

   if (p == NULL)if (p != NULL)

4、以下为Linux下的32 位C 程序,请计算sizeof 的值

char  str[] = “Hello”; 			sizeof (str) =6                       
char  *p = str; 				sizeof ( p) =4                                   
int   n = 10;					sizeof (n) =4     
void Func ( char str[100]) {  }	sizeof(str) = 4    
void * p = malloc( 100 );		sizeof (p) = 4

5、用变量a 给出下面的定义

1、一个有10个指针的数组,该指针是指向一个整型数的; int * a[10];
2、 一个指向有10个整型数组的指针; int (*a)[10]
3、 一个指向函数的指针,该函数有一个整型参数并返回一个整型数; int (*a)(int);
4、 一个有10个指针的数组,该指针指向一个函数,该函数有一个整型参数并返回一个整型数; int (*a[10])(int)

6、设有以下说明和定义

typedef union {
	long i; 
	int k[5]; 
	char c;
} DATE;

struct data{ 
	int cat; 
	DATE cow; 
	double dog;
}too;
DATE max;
printf("%d",sizeof(struct date)+sizeof(max)); 

  DATE是一个union,变量公用空间。里面最大的变量类型是int[5],占用20个字节。所以它的大小是20。data 是一个struct,每个变量分开占用空间。依次为int4 + DATE20 + double8 = 32。所以结果是20 + 32 = 52。

7、请问以下代码有什么问题

int main()
{
	char a;
	char *str=&a;
	strcpy(str,"hello");
	printf(str);
	return 0;
}

  变量a仅仅只是一个char型变量,而char *str = &a;就是让指针str指向变量a,将字符串hello拷贝到str指向的内存空间,会导致内存越界,从而会覆盖掉内存中那些不能访问的地方,最终引发系统崩溃。但可以正确输出结果。

char* s="AAA";//cosnt char* s="AAA"; is ok
printf("%s",s);
s[0]='B';
printf("%s",s);

  “AAA” 是字符串常量。s是指针,指向这个字符串常量,所以声明s的时候就有问题。cosnt char* s=“AAA”,然后又因为是常量,所以对是s[0] 的赋值操作是不合法的。

void getmemory(char *p)
{
	p = (char*)malloc(100);
	strcpy(p,“hello world”);
}
int main( )
{
	char *str=NULL;
	getmemory(str);
	printf(%s/n”,str);
	free(str);
	return 0;
} 

  程序崩溃,getmemory中的malloc 不能返回动态内存,free() 对str操作很危险。

char szstr[10];//char szstr[11]; is ok
strcpy(szstr,"0123456789");

  长度不一样,出现段错误

void main()
{
	char aa[10];
	printf(%d”,strlen(aa));
}             

  sizeof()和初不初始化,没有关系;strlen()和初始化有关,打印结果值未知。

struct A
{
   char a;
   int i;
   char b;
};

  分析:默认对齐数为4,而结构体中内存最大的为int类型,为4个字节,所以对齐的基准为4,并且结构体的内存必须是4的倍数。放下一个变量a后,使用了一个字节,因为基准是4,还剩下三个字节放不下 i 了,所以要浪费3个字节,在浪费的3个字节后放入 i 然后放入 b ,现在总字节数为1+3+4+1=9,因为总字节数要为4的倍数,所以最终大小为12。

swap( int* p1,int* p2 )
{
	int * p;  //(int)malloc(4); is ok
	*p = *p1;
	*p1 = *p2;
	*p2 = *p;
}

  p 为野指针(指向一个已删除的对象或未申请访问受限内存区域的指针)

void GetMemory(char *p) 
{ 
	p = (char *)malloc(100); 	
} 
void Test(void) 
{ 
	char *str = NULL; 
	GetMemory(str); 
	strcpy(str, "hello world"); 
	printf(str); 
} 

  程序崩溃。 因为GetMemory 并不能传递动态内存, Test 函数中的 str 一直都是 NULL。 strcpy(str, “hello world”);将使程序崩溃。—因为str没有空间。

char *GetMemory(void) 
{ 
	char p[] = "hello world";  //char *p="hello world"可以,p是局部变量,但*p是常量区的数据。
	return p; 
} 
void Test(void) 
{ 
	char *str = NULL; 
	str = GetMemory(); 
	printf(str); 
} 

  字符串是不可直接返回的,因此没用所谓的返回"hello world"之类的说法,你只能返回字符串的首地址。当你用char p[]方式定义时,系统在堆栈上创建一个临时数组,然后把hello world内容拷贝进去,因此当你返回p时,实际是返回那个临时数组的首地址。改成指针以后,p实际指向的是一个常量字符串"hello world",而这个字符串是在常量区永远存在的,不是临时变量。可以这么说,p是局部变量,但是*p不是。
  结果:可能是乱码。 因为GetMemory 返回的是指向“栈内存” 的指针,该指针的地址不是 NULL,但其原现的内容已经被清除,新内容不可知。

void GetMemory(char **p, int num) 
{ 
	*p = (char *)malloc(num); 
	printf("*p=%p\n", *p);//------------------地址1
} 
void Test(void) 
{ 
	char *str = NULL; 
	GetMemory(&str, 100); 
	printf("str=%p\n", str); //------------------地址2 == 地址1
	strcpy(str, "hello"); 
	printf(str); 
} 

  结果:能够输出hello ;内存泄漏//没有释放。

void Test(void) 
{ 
	char *str = (char *) malloc(100); 
	strcpy(str, “hello”); 
	free(str); 
	if(str != NULL) 
	{ 
		strcpy(str, “world”); 
		printf(str); 
	} 
} 

  篡改动态内存区的内容,后果难以预 料,非常危险。 因为free(str);之后,str 成为野指针, free(str);之后要加上 str=NULL
  if(str != NULL)语句不起作用

main()
{ 
	int a[5]={1,2,3,4,5};   
	int *ptr=(int*)(&a+1);   
	printf("%d,%d",*(a+1),*(ptr-1));
}

*(a+1)就是a[1],*(ptr-1)就是a[4],执行结果是2,5 &a+1不是首地址+1,系统会认为加一个a数组的偏移,是偏移了一个数组的大小(本例是5个int),则ptr实际是&(a[5]),也就是a+5 原因如下: &a是数组指针,其类型为 int (*)[5]; 而指针加1要根据指针类型加上一定的值,不同类型的指针+1之后增加的大小不同 a是长度为5的int数组指针,所以要加5*sizeof(int) 所以ptr实际是a[5] 但是prt与(&a+1)类型是不一样的(这点很重要) 所以prt-1只会减去sizeof(int*) a&a的地址是一样的,但意思不一样,a是数组首地址,也就是a[0]的地址,a+1是数组下一元素的地址,即a[1];&a是对象(数组)首地址,&a+1是下一个对象的地址,即a[5].

8、int (*s[10])(int) 表示的是什么

函数指针数组,每个指针指向一个int func(int param) 的函数。

9、c和c++ 中的struct有什么不同

1、c中struct不可以含有成员函数,而c++ 中的struct可以。
2、c++ 中struct和class的主要区别在于默认的存取权限不同,struct默认为public ,而class默认为private

10、(void*)ptr 和(*(void**))ptr 的结果是否相同

值是相同的

11、让程序跳转到绝对地址是0x100000去执行

要对绝对地址0x100000赋值,我们可以(unsigned int*)0x100000 = 1234;
那么要是想让程序跳转到绝对地址是0x100000去执行,应该怎么做?
((void (*)((void)0x10000000)( );
首先要将0x100000强制转换成函数指针,即((void)0x10000000)
然后再调用它:((void (*)(void))0x10000000)( );

12、为什么指针变量定义时一定要初始化

  答:内存空间不是你分配了才可以使用,只是你分配了之后使用才安全。如果你没对他初始化,而引用这个指针并却其指向的内存进行修改。因为指针未被初始化,所以指针所指向的也是随机的,他是个野指针,如果你引用指针并修改这个指针所指向的内容,而如果这个指针所指向的内容恰好是另外一个程序的数据的话,就会导致另外一个程序可能不能正常运行了。所以使用前一定要进行初始化。
  指针变量初始化为NULL是什么意思?
  答:强指针变量置空,初始化为NULL,使它不指向任何内容,这样引用她也不会出现上面的问题。

13、关键字volatile有什么含意

  一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。下面是volatile变量的几个例子:
1). 并行设备的硬件寄存器(如:状态寄存器);
2). 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables);
3). 多线程应用中被几个任务共享的变量。

14、在某工程中,要求设置一绝对地址为0x67a9的整型变量的值为0xaa66

  为了访问一绝对地址把一个整型数强制转换(typecast )为一指针是合法的。

int* ptr;
ptr = (int*)0x67a9;//这时候直接用*ptr报错
*ptr = 0xaa66;

15、堆栈溢出一般是由什么原因导致的

  递归过程的局部变量过多、递归深度过大,是造成系统栈溢出的原因,特别是递归列循环时肯定会发生系统栈溢出。
  递归堆栈溢出的解决方案是尾部递归优化。事实上,尾部递归和循环具有相同的效果,所以可以把循环看作是一个特殊的尾部递归函数。
  尾部递归,当函数返回时调用自身,并且返回语句不能包含表达式。通过这种方式,编译器或解释器可以优化尾部递归,这样递归本身无论被调用多少次,都只占用一个堆栈帧,而不会出现堆栈溢出。

16、引用一个已经定义过的全局变量

  以用引用头文件的方式,也可以用extern 关键字,如果用引用头文件方式来引用某个在头文件中声明的全局变理,假定你将那个变量写错了,那么在编译期间会报错,如果你用extern 方式引用时,假定你犯了同样的错误,那么在编译期间不会报错,而在连接期间报错。

17、全局变量可不可以定义在可被多个.C 文件包含的头文件中

  可以,在不同的C 文件中以static形式来声明同名全局变量。可以在不同的C文件中声明同名的全局变量,前提是其中只能有一个C文件中对此变量赋初值,此时连接不会出错。从技术上来说,声明不会有链接属性,因为声明不会在可执行映像中分配存储空间;因此,不存在链接器是否容许交叉引用那些存储空间的问题。当把(所谓的)全局变量global定义为static时,由于static使定义的变量称为内部链接,所以在各个.c文件中,存在多个同名global但不同等的定义,每个翻译单元中的global维持自己的内存区域,此时链接器不会报告“符号被多重定义”错误。此时,(所谓的)全局变量并没有达到一般意义上全局变量的效果,相当于每个翻译单元的局部变量。

18、用宏定义写出swap(x,y),即交换两数

#define swap(x, y)(x)=(x)+(y);(y)=(x)–(y);(x)=(x)–(y);

19、写一个“标准”宏,这个宏输入两个参数并返回较小的一个

#define Min(X, Y)((X)>(Y)?(Y):(X))// 结尾没有 ;分号@

20、带参宏与带参函数的区别

带参宏 带参函数
处理时间 编译时 运行时
参数类型 需定义
程序长度 变长 不变
占用存储空间
运行时间 不占运行时间 调用和返回时占

实参如果是表达式容易出问题
#define S(r )r* r
area=S(a+b);第一步换为area=r* r;,第二步被换为area=a+b* a+b;
正确的宏定义是#define S( r)(( r)*( r))
(2)宏名和参数的括号间不能有空格;
(3)宏替换只作替换,不做计算,不做表达式求解;
(4)函数调用在编译后程序运行时进行,并且分配内存。宏替换在编译前进行,不分配内存;
(5)宏的哑实结合(哑实结合类似于函数调用过程中实参替代形参的过程)不存在类型,也没有类型转换;
(6)宏展开使源程序变长,函数调用不会;
(7)宏展开不占运行时间,只占编译时间,函数调用占运行时间(分配内存、保留现场、值传递、返回值。

21、关键字volatile含意

提示编译器对象的值可能在编译器未监测到的情况下改变。

22、main()里面return 的含义

int main()
{
	int x=3;
	printf("%d",x);
	return 1;
}

mian中,c标准认为0表示成功,非0表示错误。具体的值是某中具体出错信息。

23、已知一个数组table ,用一个宏定义,求出数据的元素个数

#define NTBL(table)(sizeof(table)/sizeof(table[0]))

24、Static、全局变量、局部变量、Extern

1.static全局变量与普通的全局变量有什么区别
 static全局变量只初使化一次,防止在其他文件单元中被引用;
 全局变量(外部变量)的说明之前再冠以static 就构成了静态的全局变量。
 全局变量本身就是静态存储方式, 静态全局变量当然也是静态存储方式。 这两者在存储方式上并无不同。
 这两者的区别在于非静态全局变量的作用域是整个源程序, 当一个源程序由多个源文件组成时,非静态的全局变量在各个源文件中都是有效的。 而静态全局变量则限制了其作用域, 即只在定义该变量的源文件内有效, 在同一源程序的其它源文件中不能使用它。由于静态全局变量的作用域局限于一个源文件内,只能为该源文件内的函数公用,因此可以避免在其它源文件中引起错误。

26、static函数与普通函数有什么区别

  static函数与普通函数作用域不同,仅在本文件。只在当前源文件中使用的函数应该说明为内部函数(static修饰的函数),内部函数应该在当前源文件中说明和定义。对于可在当前源文件以外使用的函数,应该在一个头文件中说明,要使用这些函数的源文件要包含这个头文件。static函数在内存中只有一份,普通函数在每个被调用中维持一份拷贝。

27、Extern与Static

  extern和static不能同时修饰一个变量;其次,static修 饰的全局变量声明与定义同时进行,也就是说当你在头文件中使用static声明了全局变量后,它也同时被定义了;最后,static修饰全局变量的作用域只能是本身的编译单元,也就是说它的“全局”只对本编译单元有效,其他编译单元则看不到它。

28、C语言中的三大预编译功能

宏定义、文件包含、条件编译
  宏定义是C语言提供的三种预处理功能的其中一种。宏定义和操作符的区别是:宏定义是替换,不做计算,也不做表达式求解。
  文件包含在C语言中文件包含是指一个源文件可以将另一个源文件的全部内容包含进来。该命令的作用是在预编译时,将指定源文件的内容复制到当前文件中。
  条件编译指令将决定哪些代码被编译,而哪些是不被编译的。可以根据 表达式 的值或者某个特定的宏是否被定义来确定编译条件。#if、#else、#elif和#endif指令。

29、频繁使用的短小函数,在C 和C++ 中应用什么实现

c用宏定义,c++ 用inline

30、用预处理指令#define 声明一个常数,用以表明1年中有多少秒(忽略闰年问题)

#defineSECONDS_PER_YEAR(60 * 60 * 24 * 365)UL

31、在C++ 程序中调用被C 编译器编译后的函数,为什么要加extern “C”

  C++ 语言支持函数重载,C 语言不支持函数重载。函数被C++ 编译后在库中的名字与 C 语言的不同。假设某个函数的原型为:void foo(int x, int y); 该函数被C 编译器编译后在库中的名字为_foo ,而C++ 编译器则会产生像_foo_int_int之类的名字。 C++提供了C 连接交换指定符号extern“C”来解决名字匹配问题。

32、简述以下两个for 循环的优缺点

for (i=0; i<N; i++)
{
	if (condition)
		DoSomething();
	else
		DoOtherthing();
}
 程序简洁,多执行了N-1次逻辑判断,并且打断了循环“流水线”作业,
 使得编译器不能对循环进行优化处理,降低了效率。
if (condition)
{
	for (i=0; i<N; i++)
		DoSomething();
}
else
{
	for (i=0; i<N; i++)
		DoOtherthing();
}
循环的效率高程序不简洁

33、语句for( ;1 ;) 有什么问题

死循环,和while(1)相同

34、do……while和while……do区别

前一个循环一遍再判断,后一个判断以后再循环。

35、请写出下列代码的输出内容

#include 
int main()
{
	int a,b,c,d;
	a=10;
	b=a++; 		b=10, a=11
	c=++a; 		c=12, a=12
	d=10*a++;	d=120, a=13
	printf("b,c ,d:%d,%d,%d",b,c,d );
	return 0;
}
unsigned char *p1; unsigned long *p2;
p1=(unsigned char *)0x801000;
p2=(unsigned long *)0x810000;
请问p1+5= ; p2+5= ;
801005,p1是字符指针,1个字符占1字节,加5是加上5个字符所占的长度5字节;
810014,p2是长整数指针,1个长整数占4字节,加5实际是加上4x5=20字节。这个是16进制的数字,p2要加20变为16进制就是14
 int a[60][250][1000],i,j,k;
 for(k=0;k<1000;k++)
   for(j=0;j<250;j++)
      for(i=0;i<60;i++)
        a[i][j][k]=0;
把循环语句内外换一下。(编译的时候没错,运行的时候出错,但这个数组太大,
如果放在栈中,还是会溢出,要作为全局变量)
#define Max_CB 500
void LmiQueryCSmd(StructMSgCB * pmsg)
{
	unsigned char ucNum;
	for(ucNum=0;ucNum<Max_CB;ucNum++)
	{
	}                                          
}
结果死循环
unsigned char 无符号字符型表示范围0~255
char  有符号字符型 表示范围-128~127

36、用C

编写死循环。while(1){}或者for(;;)

37、一语句实现x是否为2 的若干次幂的判断

void main()                                                
{                                          
	int a;                                                         
	scanf(%d”,&a);                                    
	printf(%c”,(a)&(a-1)?’n’:’y’); //   若是打印y,否则n          
}

38、ANSI C 中断处理机制分析

  计算机内存的前1024个字节(偏移量00000H到003FFH)保存着256个中断向量,每个中断向量占4个字节,前两个字节保存着中断服务程序的入 口地址偏移量,后两个字节保存着中断程序的入口段地址,使用时,只要将它们分别调入寄存器IP及CS中,就可以转入中断服务程序实现中断调用。每当中断发生时,CPU将中断号乘以4,在中断向量表中得到该中断向量地址,进而获得IP及CS值,从而转到中断服务程序的入口地址,调用中断。这就是中断服务程序通过中断号调用的基本过程。在计算机启动的时候,BIOS将基本的中断填入中断向量表,当DOS得到系统控制权后,它又要将一些中断向量填入表中,还要修改一部分BIOS的中断向量。有一部分中断向量是系统为用户保留的,如60H到67H号中断,用户可以将自己的中断服务程序写入这些中断向量中。 在C语言中,提供了一种新的函数类型interrupt,专门用来定义中断服务程序,比如我们可以写如下的中断服务程序:

 void   interrupt   int60()     
  {     
      puts("This   is   an   example");     
  } 

如何把它的函数入口地址填写到中断向量表中,以便在产生中断的时候能转入中断服务程序去执行呢?这里要用到setvect( )getvect( )函数。setvect( )有两个参数:中断号和函数的入口地址,其功能是将指定的函数安装到指定的中断向量中,getvect ( )函数有一个参数:中断号,返回值是该中断的入口地址。在安装中断以前,最好用disable( )函数关闭中断,以防止在安装过程中又产生新的中断而导致程序运行混乱,待安装完成后,再用enable( )函数开放中断,使程序正常运行。

  #include           
  #include            
  #ifdef   __cplusplus         
  #define   __ARGU   ...         
  #else     
      
  #define   __ARGU         
  #endif         
  void interrupt int60(__ARGU)  /*中断服务函数*/         
  {     
     puts("This is an example");     
  }         
  void install(void  interrupt(*fadd)(__ARGU),int num) /*安装中断*/     
  {     
     disable();   /*关闭中断*/     
     setvect(num,fadd);   /*设置中断*/    
     enable();   /*开放中断*/     
  }     
  void main()     
  {     
     install(int60,0x60);/*将int60函数安装到0x60中断*/     
     geninterrupt(0x60);   /*人为产生0x60号中断*/     
  }     

__interrupt 关键字去定义了一个中断服务子程序(ISR)
1、 ISR 不能返回一个值。
2、 ISR 不能传递参数。
3、在许多的处理器/编译器中,浮点一般都是不可重入的。有些处理器/编译器需要让额处的寄存器入栈,有些处理器/编译器就是不允许在ISR 中做浮点运算。此外,ISR 应该是短而有效率的,在ISR 中做浮点运算是不明智的。
4、 printf() 经常有重入和性能上的问题。

39、函数重入

  在实时系统设计过程中,会出现多个任务(线程)调用同一个函数的情况。如果多个任务同时调用这个函数,有可能修改其他任务中的数据,从而导致不可遇到的后果。这个函数是不安全的,也叫不可重入函数。
  相反,可重入函数是指可以同时被多个任务调用,在调用的过程中不必担心数据是否会出错。一个可重入的函数简单来说就是可以被中断的函数(CPU保存寄存器信息到栈,跳到中断位置加载指令到寄存器执行,执行完成后,返回继续执行),也就是说,可以在这个函数执行的任何时刻中断它,转入OS调度下去执行另外一段代码,而返回控制时不会出现什么错误;而不可重入的函数由于使用了一些系统资源,比如全局变量区,中断向量表等,所以它如果被中断的话,可能会出现问题,这类函数是不能运行在多任务环境下的。
在编写可重入函数时,如果使用了全局变量,则应通过关闭中断、信号量等收到加一保护。
说明:若对所使用的全局变量不加以保护,则此函数就不具有可重入性,即当多个进程调用此函数时,很有可能使有关全局变量变为不可知状态。
保证函数的可重入性的方法:
1、在写函数时尽量使用局部变量,不使用全局变量。
2、不使用静态的数据结构,静态的数据结构,延长了变量的生命周期,使得右值(常量)变为了左值(变量)。
3、不调用标准的I/O函数(不是线程安全的)
4、不使用malloc和free(是线程安全的,但是不可重入)

40、C 语言中的整数自动转换原则

void foo(void)
{
	unsigned int a = 6;
	int b = -20;
	(a+b> 6)? puts("> 6") : puts("<= 6");
}

当表达式中存在有符号类型和无符号类型时所有的数都自动转换为无符号类型。因此-20 变成了一个非常大的正整数,所以该表达式计算出的结果大于6 。
printf("b=%u\n", b); //b=4294967276
a+b = 6+(-20) = 6 + 4294967276 = 4294967282

41、编写strcpy 函数

已知strcpy 函数的原型是 char *strcpy(char *strDest, const char *strSrc);其中strDest是目的字符串,strSrc 是源字符串。
(1)不调用C++/C 的字符串库函数,请编写函数strcpy 。
(2)strcpy 能把 strSrc 的内容复制到strDest,为什么还要char * 类型的返回值?

char * strcpy(char *dst,const char *src)   
{
    if((dst==NULL)||(src==NULL))
           return NULL; 
 
    char *ret = dst; 
    
    while ((*dst++=*src++)!='\0'); 
    dst已经移动,ret返回首指针
    return ret;
}

(1)const 修饰:源字符串参数用const修饰,防止修改源字符串;
(2)空指针检查:源指针和目的指针都有可能会出现空指针的情况,所以应该对其进行检查;
(3)为什么要设置ret 指针以及返回ret指针的位置,由于目的指针dst已经在进行移动了,所以用辅助指针ret表明首指针;

42、二分查找的代码

int binary_search(int* arr, int key, int n)
 {
    int low=0;
    int mid;
    int high=n-1;
    while(low<=high)
    {
        mid = (low+high)/2;
        if(key < arr[mid])
            high=mid-1;
        else if(key>arr[mid])
            low=mid+1;
        else
            return mid;
    }
    return -1;
 }
 
int main(int argc, char *argv[])
{
	int it[] = {0,1,2,3,4,5,6,7};
	int index = binary_search(it, 7, 8);
	printf("index=%d\n", index); //index=7
 
    return 0;
}

43、编写一个C 函数,该函数给出一个字节中被置 1 的位的个数

unsigned int TestAsOne1(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;
}

44、编写一个C 函数,该函数将给定的一个字符串转换成整数

int Invert2(char* str)
{
    if(str==NULL)
        return -1;
        
    int num=0, negFlag=1;
    if(str[0] == '-')
    {
        negFlag=-1;
        str++;
    }
    while(*str!='\0')
    {
        if(*str>='0' && *str<='9')
            num = num*10 + (*str-'0');
        else
        {
            num=-1;
            break;
        }
        str++;
    }
    num *= negFlag;
    
    return num;
}

45、实现strcmp 函数

//str1和str2可以是字符串常量或者字符串变量,返回值为整形。返回结果如下规定:
//①str1小于str2,返回负值或者-1(VC返回-1);str1等于str2,返回0;
//③str1大于str2,返回正值或者1(VC返回1);
int mystrcmp(const char* str1, const char* str2)
{
    assert((str1 != NULL) && (str2 != NULL));
    int ret=0;
    while((ret=*(unsigned char*)str1-*(unsigned char*)str2)==0 && *str2)
    {
        str1++;
        str2++;
    } 
    
    if (ret > 0)
        ret = 1;
    else if (ret < 0)
        ret = -1;
        
    return ret ;
} 

46、字符串逆序

void AntitoneValue(char* father, char* child)
{
    if(father == NULL)
        return;
 
    int len = strlen(father);
    int i;
    for(i=0; i<len; i++)
    {
        child[i]=father[len-i-1];
    } 
    child[len]='\0';
}
int main(int argc, char *argv[])
{
    char *str1="dongj1223";
    char str2[100];
    AntitoneValue(str1, str2);
    cout << str2 << endl;
    return 0;
}

47、编写一个C 函数,该函数在给定的内存区域搜索给定的字符,并返回该字符所在位置索引

int search(char* cpSource, int n , char ch) // 起始地址,搜索长度,目标字符
{
    int res; 
    int i;
    for(i=0; i<n && *(cpSource+i)!=ch; i++)
    {   
        ;   //不相等,则继续;相等则退出;超出范围也退出;
    } 
    
    if(i==n)
        res = -1; //考虑找不到的情况,返回-1
    else
        res = i;  //找到,则返回索引值
    
    return res;
}

48、实现双向链表删除一个节点P,在节点P 后插入一个节点,写出这两个函数

// 删除操作
Status ListDelete_DuL(DuLinkList &L,inti,Ele mType &e)
{
	if(!(p=GetElemP_DuL(L,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,inti,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;
}

49、二维数组行列元素互换,存到另一个数组

//二维数组行列元素互换,存到另一个数组中
void convertArry()
{
    int a[2][3]={{1,2,3},{4,5,6}};
    int b[3][2];
    int i,j;
    printf("a:\n");
    for(i=0; i<2; i++)
    {
        for(j=0; j<3; j++)
        {
            printf("%d ", a[i][j]);
            b[j][i]=a[i][j];
        }
        printf("\n");
    }
    printf("b:\n");
    for(i=0; i<3; i++)
    {
        for(j=0; j<2; j++)
        {
            printf("%d ", b[i][j]);
        }
        printf("\n");
    }
}

50、写一个内存拷贝函数

void* memcpy(void* pvTo, const void* pvFrom, size_t size)
{
	assert((pvTo != NULL) && (pvFrom ! = NULL));
	byte* pbTo= pvTo;
	
	byte* pbFrom = pbFrom;
	while (size-- >  0)
	{
		*pbTo++ = *pbFrom++;
	}
	return pvTo;
}

linux 系统

1、Makefile 的规则

target…:dependecies…
  command
target也就是一个目标文件,可以是Object File,也可以是执行文件。
prerequisites就是,要生成那个target所需要的文件或是目标;
command也就是make需要执行的命令。(任意的Shell命令)。
注意事项: 1.命令要以[Tab]为开始 2.有clean

2、gcc常用编译选项

-o:产⽣⽬标(.i、.s、.o、可执⾏⽂件等)
-c:通知gcc 取消链接步骤,即编译源码并在最后⽣成⽬标⽂件
-E:只运⾏C 预编译器
-S:控制GCC 编译器仅对指定文件处理至编译阶段
-Wall:使gcc 对源⽂件的代码有问题的地⽅发出警告,开启所有错误提示,可理解为warinig all
-Idir:将dir ⽬录加⼊搜索头⽂件的⽬录路径
-Ldir:将dir ⽬录加⼊搜索库的⽬录路径
-llib:链接lib 库
-g:在⽬标⽂件中嵌⼊调试信息,以便gdb 之类的调试程序调试
-I:指定头文件路径;如 gcc -I./include
-D:定义一个宏;如 gcc -DHAVE_CONFIG_H,定义宏HAVE_CONFIG_H
-O2:指定编译优化等级为2,optimization
-pipe:指定编译过程中不同阶段的通信使用pipe管道(有些编译器无法读取管道,目前GNU编译器是ok的)
-fexceptions:启用异常处理,会产生额外的代码用于处理异常,会占用一定量的数据空间(gcc默认为C++打开该选项,为C关闭该选项)
-fstack-protector:开启栈保护检测,防止缓冲区异常
-m64:指定生成64位的x86-64架构代码
-mtune=generic:为指定的CPU架构优化代码
-fPIC:生成位置无关的代码,适用于动态链接
-fPIE:为可执行文件生成位置无关代码

3、GCC编译步骤

#include 
#define NAME "mao"
void main(){
 printf("hello:%s\n",NAME);
}

1、预处理阶段
 预处理阶段主要工作是删除程序中所有的注释、处理以# 开头的命令,如:头文件的展开、宏定义的替换。
 对于上面的代码#include 表示引入头文件,在预处理阶段stdio.h文件就会被加载到我的hello.c中;而对于在代码中使用到的NAME,就会被替换成该宏所定义的真实内容"mao"。可以使用下面的命令执行预处理:生成.i文件,linux系统中预处理后所得文件的后缀名通常是 .i
gcc -E hello.c -o hello.i

2、编译阶段
 gcc在编译阶段会对预处理后的代码进行各种检查(词法分析、语法分析、语义分析),来判断我们代码是否符合规范,如果通过就会生成汇编代码(以.s结尾的文件)
 注意:gcc -S 不是只能编译经过预处理后的.i文件,它的涵义是控制GCC 编译器仅对指定文件处理至编译阶段。
 如果需要编译的文件是经过预处理后的 .i 文件,则 GCC 编译器只需编译此文件。
 如果需要编译的文件是 .c 或者 .cpp 源文件,则 GCC 编译器会对其进行预处理和编译(两步)。
gcc -S hello.i -o hello.s
3、汇编阶段
 把 .s 文件翻译成二进制.o文件(目标文件)
 注意:gcc -c 不是只能编处理.s文件,它的涵义是控制GCC 编译器仅对指定文件处理至汇编阶段。
 如果指定文件为.c文件,则 gcc -c 指令会对 该文件执行预处理、编译、汇编。
 如果指定文件为经过预处理后的.i文件,则 gcc -c 指令对该文件执行编译和汇编。
 如果指定文件为经过编译后的文件,则 gcc -c 指令只对该文件执行汇编操作。
gcc -c hello.s -o hello.o
4、链接阶段
 汇编阶段将代码编译成了二进制文件,还需要和系统其他组件(比如标准库、动态链接库等)结合起来才能正常运行,比如调用print函数打印,在预处理阶段也只是将“stdio.h”头文件中的申明引入进来,没有函数的实现,那怎么调用它的呢?这就是链接的工作了,链接之前的操作都是对一个文件进行处理,而链接可以看作是对多个文件进行“打包”的过程,它将所有的目标文件以及系统组件组合成一个可执行文件。
gcc hello.o -o hello

4、GDB

GDB 主要完成下⾯三个⽅⾯的功能:
1.启动调试程序;
2.让被调试的程序在指定的位置停住(即可以设置断点);
3.当成被停住时,可以检查程序的状态(如变量值等等);
启动GDB 很简单,gdb 调试程序名;
 例如 :gdb a;
 或者:gdb;
  file 可执⾏⽂件名;
 例如:gdb;
  file a;
注意:GDB 中的这些命令是有全名和缩写,在括号内的为缩写;
help :显示gdb 的使⽤帮助信息,后可接命令名,显示命令的使⽤信息;
list(l):查看源程序;
bt :显示程序的堆栈;
print vars:打印此时vars 变量的值;
break(b) 函数名 :在某函数⼊⼝处添加断点;
break(b) ⾏号 :在指定⾏添加断点;
break(b) ⽂件名 ⾏号 :在指定⽂件中的指定⾏添加断点;
break(b) ⾏号 if 条件 :当条件为真时,指定⾏号出断点⽣效;
 例如:b 5if i = 10, 当i = 0 成⽴时,在第5⾏的断点⽣效;
info break  :查看所有设置的断点;
delete 断点编号  :删除断点;
run( r) :开始运⾏程序;
next(n) :单步运⾏程序(不进⼊⼦函数);
step(s) :单步运⾏程序(进⼊⼦函数);
continue( c) :继续运⾏程序;
quit :退出gdb 调试程序;
p/print:打印指定变量(临时变量和全局变量)、字符串、表达式等值;
i/info:显示各类信息;
q:退出gdb 调试环境;
clear:删除⼀个断点,这个命令需指定代⾏或者函数名;
set:设置变量的值。如set val = 2即为把2保存到val 变量中。

5、Linux 常⽤命令

touch:创建空⽂件;
echo:创建带有内容的⽂件;
cat:查看⽂件内容;
mv 移动或重命名;
find:在⽂件系统中搜索某⽂件;
wc:统计⽂本中⾏数、字数、字符数;
grep:在⽂本⽂件中查找某个字符串;
rmdir:删除空⽬录;
pwd:显示当前⽬录;
ln:创建链接⽂件;
more、less:分⻚显示⽂本⽂件内容;
head、tail:显示⽂件头、尾内容;
ctrl+alt+F1:命令⾏全屏模式;
系统管理命令
stat:显示指定⽂件的详细信息,⽐ls 更详细;
who:显示在线登陆⽤户;
whoami:显示当前操作⽤户;
hostname:显示主机名;
uname:显示系统信息;
top:动态显示当前耗费资源最多进程信息;
ps:显示瞬间进程状态 ps -aux;
du:查看⽬录⼤⼩ du -h /home 带有单位显示⽬录信息;
df:查看磁盘⼤⼩ df -h 带有单位显示磁盘信息;
ping:测试⽹络连通;
netstat:显示⽹络状态信息;
man:命令不会⽤了 如:man ls;
alias:对命令重命名;
如:alias showmeit=“ps -aux” ,另外解除使⽤unaliax showmeit;
kill:杀死进程,可以先⽤ps 或 top 命令查看进程的id,然后再⽤kill 命令杀死进程。

6、linux 权限管理

chmod u-rwx xxx #取消xxx ⽬录,⽤户“读写执⾏”权限
 + 添加权限
 -取消权限
 Linux 系统⼀般将⽂件可存/取访问的身份分为3个类别:owner(拥有者)、group(和
所有者同组的⽤户)、others(其他⼈,除了所有者,除了同组的⽤户以及除了超级管理员),
且3种身份各有read(读)、write(写)、execute(执⾏)等权限。
修改⽂件的所有者:chown
 chown[OPTION]…[OWNER][:[GROUP]] FILE…
以上只有root 才能修改⽂件的所有⼈:group或 .group ⽂件的owner 也可以使⽤chown修改
⽂件的所属组(owner 必须属于⽬标组)
修改⽂件的所有组:chgrp
 chgrp [option] group filename
rwx 权限所对应的数字意义:
 — 000 0 --x 001 1 -w- 010 2 -wx 011 3
 r-- 100 4 r-x 101 5 rw- 110 6 rwx 111 7
例 chmod 777 file(777即111111111 为file ⽂件设置了rwxrwxrwx 权限)
 111 111 111
 rwx rwx rwx

7、Shell 编程

1、shell脚本的执行方式

shell ⽂件的第⼀⾏: #!/bin/sh 或 #!/bin/bash(脚本需要有可执行权限)
#! 符号能够被内核识别成是⼀个脚本的开始,这⼀⾏必须位于脚本的⾸⾏。
/bin/bash 是bash 程序的绝对路径,在这⾥表示后续的内容将通过bash 程序解释执⾏。
chmod +x ./test.sh #使脚本具有执⾏权限
./test.sh #执⾏脚本

2、shell变量

set :显示当前Shell 进程中定义的所有变量(包括本地变量和环境变量)和函数。
unset :删除已定义的环境变量或本地变量。
printenv :显示当前Shell 进程的环境变量。
声明静态变量:readonly 变量(静态变量声明后不能unset 撤销)
命令test 或 [ 可以测试⼀个条件是否成⽴,如果测试结果为真,则该命令的Exit Status 为0,
如果测试结果为假,则命令的Exit Status 为1(注意与C 语⾔的逻辑表示正好相反)。
系统变量:$HOME、$PWD、$SHELL、$USER
定义变量的规则
a. 变量名称可以由字母、数字和下划线组成,但是不能以数字开头
b. 等号两侧不能有空格
c.变量名称一般习惯为大写
环境变量
export 变量名=变量值(功能描述:将shell变量输出为环境变量/全局变量)
source 配置文件(功能描述:让修改后的配置信息立即生效)
echo 显示⽂本⾏或变量,或者把字符串输⼊到⽂件。
echo [option] string
 -e 解析转义字符
 -n 不回⻋换⾏。默认情况echo 回显的内容后⾯跟⼀个回⻋换⾏。
和C 语⾔类似,Shell 中也有函数的概念,但是函数定义中没有返回值也没有参数列表。
grep 命令是⼀种强⼤的⽂本搜索⼯具,它能使⽤正则表达式搜索⽂本
find 命令的⼀般形式为
find pathname -options [-print -exec -ok ...]
位置参数变量
$n:n为数字,$0代表命令本身,$1-$9代表第一到第九个参数,十以上的参数需要用大括号包含,如${10}。相当于C 语⾔main 函 数 的argv[1]、argv[2]…
$#:这个变量代表命令行中所有的参数个数
$@ :表示参数列表"$1" "$2" ...,例如可以⽤在for 循环中的in 后⾯。
$* :表示参数列表"$1" "$2" ...,同上
预定义变量
shell设计者事先已经定义好的变量,可以直接在shell脚本中使用
$$:当前进程的进程号(PID)
$!+:后台运行的最后一个进程的进程号(PID)
$?:最后一次执行的命令的返回状态。如果这个变量的值为0,证明上一个命令正确执行;如果这个变量的值为非0(具体是哪个数,由命令自己来决定),则证明上一个命令执行不正确了。

流程控制

注意condition前后要有空格

if [ condition ]
then 
	语句
elif
then 
	语句
fi
for 变量 in 值1 值2 值3...  
(或for((初始值;循环控制条件;变量变化))
do
	程序
done
while [ 条件判断式 ]
do
	程序
done
自定义函数调用
#!/bin/bash
 function Sum()
{
	SUM=$[ $n1 + $n2 ];
	echo "SUM=$SUM"
}
#输入两个值
read -p "值1=" n1
read -p "值2=" n2

Sum $n1 $n2

8、回调函数

1.什么是回调函数:
 函数指针的调用,即是一个通过函数指针调用的函数;
如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,就说这是回调函数。
即:把一段可执行的代码像参数传递那样传给其他代码,而这段代码会在某个时刻被调用执行,就叫做回调。如果代码立即被执行就称为同步回调,如果在之后晚点的某个时间再执行,则称为异步回调。
2.为什么要使用回调函数
 回到函数作用:“解耦”,普通函数代替不了回调函数的这个特点。这是回调函数最大的特点。
使用回调函数,和普通函数调用区别:1)在主入口程序中,把回调函数像参数一样传入库函数。这样一来,只要我们改变传进库函数的参数,就可以实现不同的功能,且不需要修改库函数的实现,变的很灵活,这就是解耦。
2)主函数和回调函数是在同一层的,而库函数在另外一层。如果库函数对我们不可见,我们修改不了库函数的实现,也就是说不能通过修改库函数让库函数调用普通函数那样实现,那我们就只能通过传入不同的回调函数了,这也就是在日常工作中常见的情况。
3.回调函数:使用其实也是不得已而为之,是一种解决问题的策略
 回调函数的使用是对函数指针的应用,函数指针的概念本身很简单,但是把函数指针应用于回调函数就体现了一种解决问题的策略,一种设计系统的思想

回调函数的缺点:
1)回调函数固然能解决一部分系统架构问题但是绝不能再系统内到处都是,如果你发现你的系统内到处都是回调函数,那么你一定要重构你的系统。
2)回调函数本身是一种破坏系统结构的设计思路,回调函数会绝对的变化系统的运行轨迹,执行顺序,调用顺序。回调函数的出现会让读到你的代码的人非常的懵头转向。

9、const与宏的区别

1、编译检查:宏不会编译检查,const有编译检查;
2、宏的好处:宏可以定义函数、方法等,const不可以;
3、宏的缺点:大量使用宏,会导致预编译的时间过长。

10、malloc()与calloc()的区别

1、malloccalloc都是从堆上动态申请内存空间;
2、malloc只有一个参数,即要分配的内存大小;
3、calloc函数有两个参数,分别是元素的个数与元素的大小;
4、malloc不能对内存初始化,calloc堆内存的每一位初始化为零。

11、strcpy、sprint和memcpy的区别

1、strcpy函数操作的对象是字符串,完成从源字符串到目的字符串的拷贝;
2、sprint函数操作的对象不限于字符串,是实现其他数据类型先给高字符串的转化,方法中需要指定源对象的数据类型,如果源对象是字符串,也可以实现字符串的拷贝功能;
3、memcpy函数是内存的拷贝,实现将一个内存的内容复制到另一个内存块。内存块由首地址及长度决定;
4、strcpy为什么要返回char *类型?增加代码的灵活性,方便其他函数直接调用。

12、引用与指针区别

1、引用必须被初始化,指针不必;
2、引用初始化以后不能被改变,指针可以改变所指的对象;
3、不存在指向空值的引用,但是存在指向空值的指针。

13、关键字static的作用

在C语言中,关键字static有三个明显的作用:
1、在函数体,一个被声明为静态的变量在这一函数被调用过程中维持其值不变;
2、在模块内(但在函数体外),一个被声明为静态的变量可以被模块内所用函数访问,但不能被模块外其它函数访问。它是一个本地的全局变量;
3、在模块内,一个被声明为静态的函数只可被这一模块内的其它函数调用。那就是,这个函数被限制在声明它的模块的本地范围内使用。

14、static全局变量与普通的全局变量区别?static函数与普通函数区别?

 全局变量(外部变量)的说明之前再冠以static 就构成了静态的全局变量。全局变量本身就是静态存储方式,静态全局变量也是静态存储方式。这两者在存储方式上并无不同。
 这两者的区别虽在于非静态全局变量的作用域是整个源程序, 当一个源程序由多个源文件组成时,非静态的全局变量在各个源文件中都是有效的。而静态全局变量则限制了其作用域,即只在定义该变量的源文件内有效, 在同一源程序的其它源文件中不能使用它。
 由于静态全局变量的作用域局限于一个源文件内,只能为该源文件内的函数公用,因此可以避免在其它源文件中引起错误。
从以上分析可以看出,把局部变量改变为静态变量后是改变了它的存储方式即改变了它的生存期。把全局变量改变为静态变量后是改变了它的作用域,限制了它的使用范围。
 static函数与普通函数作用域不同,仅在本文件。只在当前源文件中使用的函数应该说明为内部函数(static),内部函数应该在当前源文件中说明和定义。对于可在当前源文件以外使用的函数,应该在一个头文件中说明,要使用这些函数的源文件要包含这个头文件。

15、进程和线程区别

进程是并发执行的程序在执行过程中分配和管理资源的基本单位。线程是进程的一个执行单元,是比进程还要小的独立运行的基本单位。一个程序至少有一个进程,一个进程至少有一个线程。两者的区别主要有以下几个方面
1、进程是资源分配的最小单位。
2、线程是程序执行的最小单位,也是处理器调度的基本单位,但进程不是,两者均可并发执行。
3、进程有自己的独立地址空间,每启动一个进程,系统就会为它分配地址空间,建立数据表来维护代码段、堆栈段和数据段,这种操作非常昂贵。而线程是共享进程中的数据,使用相同的地址空间,因此,CPU切换一个线程的花费远比进程小很多,同时创建一个线程的开销也比进程小很多。
4、线程之间的通信更方便,同一进程下的线程共享全局变量、静态变量等数据,而进程之间的通信需要以通信的方式(IPC)进行。不过如何处理好同步与互斥是编写多线程程序的难点。但是多进程程序更健壮,多线程程序只要有一个线程死掉,整个进程也跟着死掉了,而一个进程死掉并不会对另外一个进程造成影响,因为进程有自己独立的地址空间。
5、进程切换时,消耗的资源大,效率低。所以涉及到频繁的切换时,使用线程要好于进程。同样如果要求同时进行并且又要共享某些变量的并发操作,只能用线程不能用进程。
6、执行过程:每个独立的进程有一个程序运行的入口、顺序执行序列和程序入口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
 优缺点:
线程执行开销小,但是不利于资源的管理和保护。线程适合在SMP机器(双CPU系统)上运行。
进程执行开销大,但是能够很好的进行资源管理和保护,可以跨机器迁移。
 何时使用?
对资源的管理和保护要求高,不限制开销和效率时,使用多进程。
要求效率高,频繁切换时,资源的保护管理要求不是很高时,使用多线程。

16、什么是预编译,何时需要预编译

1、总是使用不经常改动的大型代码体;
2、程序由多个模块组成,所有模块都使用一组标准的包含文件和相同的编译选项。在这种情况下,可以将所有包含文件预编译为一个预编译头;
3、 预编译指令指示了在程序正式编译前就由编译器进行的操作,可以放在程序中的任何位置。

char * const p char const * p const char *p 上述三个区别

char * const p; //常量指针,p的值不可以修改
char const * p;const char *p;//指向常量的指针,指向的常量值不可以改

为什么使用++i而不是i++

 对于内置类型的后置++操作,编译器会进行优化,而对于非内置类型存,则不会进行优化,性能一致。迭代器的前置和后置操作主要有以下两个区别:
返回值:前置操作返回对象的引用,后置操作返回类型为对象,
拷贝:前置操作无拷贝操作,后置操作存在一次对象拷贝。
for循环中 i++ 和 ++i 的使用效果是一样的
 区别:for循环中 i++的耗时比++i的要长一些。数据量大些时候就比较明显了;
 原因:for循环中 i++ 在处理时,i++实际为i = i+1,执行时先创建临时变量保存 i 值,然后再+1,而++i不需要的,没有这个过程,所以++i的性能高于i++;
 影响:for循环中 i++由于要创建临时变量并保存i的值,所以需要占用内存,使用完后释放内存,一个是造成资源占用,一个是数据量大时,造成程序性能低;

你可能感兴趣的:(c语言,开发语言)