备注: 凡是在《程序员面试宝典3》上面有的题目,在此不重复列出了。
8. 描述实时系统的基本特性
华为
static全局变量与普通的全局变量有什么区别:static全局变量只初使化一次,防止在其他文件单元中被引用; 2.对于一个频繁使用的短小函数,在C语言中应用什么实现,在C++中应用什么实现?
慧通: 一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值(From Memory),而不是使用保存在寄存器里的备份。 下面是volatile变量的几个例子: 1) 并行设备的硬件寄存器(如:状态寄存器) 2) 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables) 3) 多线程应用中被几个任务共享的变量 这个多用在嵌入式开发中,一般场合不需要使用。
|
常用运算符优先级:
C语言基本数据类型
数据类型修饰符
signed:有符号 unsigned:无符号
short:短型 long:长型
在基本的数据类型前可以添加修饰符,以改变基本类型的意义。
unsigned和signed只用于修饰char和int,且signed修饰词可以省略。当用unsigned修饰词时,后面的类型说明符可以省略。
例如:signed int n; //与“int n;”等价
signed char ch; //与“char ch;”等价
unsigned int n; //与“unsigned n;”等价
unsigned char ch; //与“unsigned ch;”等价
short只用于修饰int,且用short修饰时,int可以省略。
即:short int n; //与“short n;”等价
long只能修饰int和double。当用long修饰int时,int可以省略。
即:long int n; //与“long n;”等价
int和unsigned int 类型占用一个机器一个字(word)的字节。在16位操作系统上,它们占用2个字节;在32位操作系统上,它们占用4个字节。用sizeof(数据类型)可以确定某数据类型的字节长度。
基本的数据类型及其表示范围,可参见图:
字符串常见算法
1 怎样将整型数转化为字符串数,并且不用函数itoa?
#include<iostream> #include<stdio.h> int main(void) { int num=12345,j=0,i=0; chartemp[7],str[7]; while(num) {temp[i]=num%10+'0'; i++; num=num/10; } temp[i]=0; printf("temp=%s\n",temp); i=i-1; printf("temp=%d\n",i); while(i>=0) { str[j]=temp[i]; j++; i--; } str[j]=0; printf("string=%s\n",str); return0; }
2 编程实现字符串数转化成整数的办法
enum Status {kValid = 0, kInvalid};
int g_nStatus = kValid; //非法输入标识符,非法输入则返回0
///////////////////////////////////////////////////////////////////////
// Convert a string into an integer
///////////////////////////////////////////////////////////////////////
int StrToInt(const char* str)
{
g_nStatus = kInvalid; //最开始设置为无效(0),判读是否为非法输入,和有“0”输入情况下的返回判断;如果的确是0,则在后面更改为最初的值kValid。
long long num = 0;
if(str != NULL && *str != '\0')
{
// the first char in the string maybe '+' or '-' 预处理第一个字符“+ -号”
bool minus = false;
if(*str == '+')
str ++;
else if(*str == '-')
{
str ++;
minus = true;
}
if (*str != '\0')
{
num = StrToIntCore(str, minus); //对字符串看开始处理
}
}
return (int)num;
}
long long StrToIntCore(const char * digit, bool minus);
{
long long num=0;
// the remaining chars in the string
while(*digit != '\0')
{
if(*digit >= '0' && *digit <= '9') //如果是数字字符串,则开始处理了。
{ int flag = minus ? -1 : 1;
num = num * 10 + flag * (*digit - '0');
// overflow
if((!minus && num > 0x7FFFFFFF)|| (minus && num < (signed int )0x80000000))
{
num = 0;
break;
}
digit ++;
}
// if the char is not a digit, invalid input 如果不是字符串,则是非法输入,直接返回num,且这是g_nStatus为无效值kInvalid(1)
else
{
num = 0;
break;
}
}// while(*digit != '\0')
if(*digit == '\0')
{
g_nStatus = kValid; //如果是数字字符(包括只含0的情况),直接返回num,且这是g_nStatus为kValid依然为原来的值kValid(0)。
}
return num;
}
3 编程实现函数strcpy,不调用C/C++的字符串库函数
char *strcpy(char *strDest, const char *strSrc) { assert((strDest!=NULL)&&(strSrc!=NULL)); char *address=strDest; while((*strDest++=*strSrc++)!='\0'); return address; }
4 编写一个函数,作用是把一个char组成的字符串循环右移n个
void LoopMove(char *pStr, int steps) { int n=strlen(pStr) - steps; char tmp[MAX_LEN]; memcpy(tmp, pStr+n, steps); memcpy(pStr+steps, pStr, n); memcpy(pstr, tmp, steps); }
5 将一句话里的单词进行倒置,标点符号不倒换。比如一句话“I love csdn." 倒换后变成"csdn.love I"。
#include<iostream> #include<stdio.h> using namespace std; int main(void) { int j=0, i=0, flag=0, begin, end; char str[]="i come from tianjin.", temp; j=strlen(str)-1; printf("string =%s\n", str); //第一步是进行全盘翻转,将单词变成.nijnait morf emoc i while(j>i) { temp=str[i]; str[i]=str[j]; str[j]=temp; j--; i++; } printf("string = %s\n", str); i=0; //第二步进行部分翻转,如果不是空格,则开始翻转单词 while(str[i]) { if(str[i]!=' ') { begin=i; while(str[i]&&str[i]!=' ') {i++;} i=i-1; end=i; } while(end>begin) { temp=str[begin]; str[begin]=str[end]; str[end]=temp; end--; begin++; } i++; } printf("string = %s\n", str); return0; }
6 转换字符串格式为原来字符串里的字符+该字符连续出现的个数,例如字符串1444223,转换为11432231(1出现1次,4出现3次,2出现2次.....)
#include<iostream> #include<string> using namespace std; int main() { cout<<"Enterthe numbers "<<endl; string str; char reschar[50]; reschar[0]='\0'; cout<<"I am here 1"<<endl; getline(cin,str); cout<<"I am here 2"<<endl; int len=str.length(); cout<<"I am here 3"<<endl; int count=1; int k; for(k=0;k<=len-1;k++) { cout<<"I am here 4"<<endl; if((k+1)<len) { if((str[k+1]==str[k])) { cout<<"I am here 51"<<endl; count++; } else { cout<<"I am here 52"<<endl; sprintf( reschar+ strlen(reschar),"%c%d", str[k], count); count=1; } }//if((k+1)<len) else break; } sprintf( reschar+ strlen( reschar),"%c%d", str[k], count); cout<<"Iam here 7"<<endl; cout<<reschar<<"gg"<<endl; cout<<endl; return0; }
7 编程实现字符串比较函数strcmp(char *src, char *sub)
intstr_cmp(char *s1, char *s2) { while((*s1!='\0')|| (*s2!='\0')) { if(*s1==*s2) { s1++; s2++; } else if(*s1<*s2) { return -1; } else return 1; } return 0; }
下面几个题目在《编程之美》上面出现过,但是由于非常经典,而且面试时出现的概率特别大,所以自己要特别拿出来总结。
第一题: 1 的数目
给定一个十进制正整数N, 写下从1开始,到N的所有整数,然后数一下其中出现所有“1”的个数,即求f(N)。
例如:
N=2, 写下1,2,。 出现1个1;
N=13, 我们写下:1,2,3,4,5,6,7,8,9,10,11,12,13,出现1个的个数是6.
求解函数f(N),即返回1 到N之间出现的 1 的个数,如f(13)=6.
[cpp] view plaincopy
第二题: 求二进制数中1 的个数
对于一个字节(8bit)的无符号整型变量,求其二进制表示中“1”的个数,要求算法的执行效率尽可能高。
[cpp] view plaincopy
第三题: 最大公约数问题
写一个程序,求两个正整数的最大公约数。如果两个正整数都很大,有什么简单的方法吗?
求最大公约数是一个很基本的问题。早在公元前300年左右,欧几里得就在他的著作《几何原本》中给出了高效的解法——辗转相除法。辗转相除法使用到的原理很聪明也很简单,假设用f(x, y)表示x,y的最大公约数,取k= x/y,b = x%y,则x = ky + b,如果一个数能够同时整除x和y,则必能同时整除b和y;而能够同时整除b和y的数也必能同时整除x和y,即x和y的公约数与b和y的公约数是相同的,其最大公约数也是相同的,则有f(x, y)=f(y, y % x)(y > 0),如此便可把原问题转化为求两个更小数的最大公约数,直到其中一个数为0,剩下的另外一个数就是两者最大的公约数。辗转相除法更详细的证明可以在很多的初等数论相关书籍中找到,或者读者也可以试着证明一下。
示例如下:
f(42, 30)=f(30, 12)= f(12, 6)=f(6, 0)= 6
【解法一】
最简单的实现,就是直接用代码来实现辗转相除法。从上面的描述中,我们知道,利用递归就能够很轻松地把这个问题完成。
具体代码如下:
[cpp] view plaincopy
【解法二】
在解法一中,我们用到了取模运算。但对于大整数而言,取模运算(其中用到除法)是非常昂贵的开销,将成为整个算法的瓶颈。有没有办法能够不用取模运算呢?
采用类似前面辗转相除法的分析,如果一个数能够同时整除x和y,则必能同时整除x-y和y;而能够同时整x-y和y的数也必能同时整除x和y,即x和y的公约数与x-y和y的公约数是相同的,其最大公约数也是相同的,即f(x, y)= f(x-y, y),那么就可以不再需要进行大整数的取模运算,而转换成简单得多的大整数的减法。
在实际操作中,如果x<y,可以先交换(x, y)(因为(x, y)=(y, x)),从而避免求一个正数和一个负数的最大公约数情况的出现。一直迭代下去,直到其中一个数为0。
示例如下:
f(42, 30)=f(30, 12)=f(12, 18)= f(18, 12)= f(12, 6)= f(6, 6)= f(6, 0)= 6
解法二的具体代码如下:
代码清单2-15
[cpp] view plaincopy
第四题: 求数组的子数组之和的最大值
写一个程序,求两个正整数的最大公约数。如果两个正整数都很大,有什么简单的方法吗?
一个有N个整数元素的一维数组( A[0], A[1], ... , A[n-2], A[n-1]),子数组之和的最大值是什么?(要求子数组的元素是连续的)
例子:有数组( -2, 5, 3, -6, 4,-8, 6),则其子数组之和的最大值为8,其对应的数组为(5,3)
解法一:采用直接法,记Sum[i...j],为数组A中从第i到第j之间所有数之和,算出所有Sum,取其最大,代码如下,时间复杂度O(N2):
[cpp] view plaincopy
命令行参数
Int main(int argc,char *argv[ ])
argv为指针的指针,argc为整数
char **argv or:char *argv[] or: char argv[][]
main()括号内是固定的写法。
下面给出一个例子来理解这两个参数的用法:
假设程序的名称为prog,
当只输入prog,则由操作系统传来的参数为:
argc=1,表示只有一程序名称,argc只有一个元素,
argv[0]指向输入的程序路径及名称:./prog
当输入prog para_1 para_2 ,有2个参数,则由操作系统传来的参数为:
argc=3,表示除了程序名外还有2个参数。
argv[0]指向输入的程序路径及名称。
argv[1]指向参数para_1字符串。
argv[2]指向参数para_2字符串。
void main( intargc, char *argv[] )
char *argv[] :argv 是一个指针数组,他的元素个数是argc,存放的是指向每一个参数的指针,
他的第一个元素即argv[0]为编译生成的可执行文件名(包括路径eg:"F:/VC/Ex1/Debug/Ex1.exe"),
从二个元素(argv[1])开始,
是每一个参数 int argc表示argv的大小,是实际参数个数+1,
其中+1是因为argv[0]是编译后的可执行文件名
命令行界面的程序,通常都需要输入命令行参数帮助程序执行。假定有一个可执行程序名为test。那么运行该程序的的命令行如下:
test
带命令行参数是同一行中的附加项:
test –c TEST
其中 –c 和 TEST就是命令行参数。C程序可以将这些附加参数读出来,并为自己所用,比如作为程序运行的条件(经常看到调试参数 –D 就是这么一个)。C程序通过使用main()的参数来读取这些附加参数,下面的repeat.c给出一个读出main参数的例子:
repeat.c:
#include <stdio.h> #include <stdlib.h> int main(int argc, char *argv[]) { int count; printf("The command line has %darguments:/n", argc - 1); for(count = 1; count < argc; count++) { printf("%d: %s/n", count,argv[count]); } printf("/n"); //system("PAUSE"); return 0; }
C语言中三个动态内存分配函数
malloc函数
原型:extern void *malloc(unsigned int num_bytes);
用法:#include<stdlib.h>
功能:分配长度为num_bytes字节的内存块
说明:如果分配成功则返回指向被分配内存的指针,否则返回空指针NULL。
当内存不再使用时,应使用free()函数将内存块释放。
举例:
// malloc.c
#include <stdio.h> #include <stdlib.h> main() { char *p; p=(char *)malloc(100); //后面必须是一个整型数,表示多少字节 if(p) //判断是否为空 printf("MemoryAllocated at: %x",p); else printf("NotEnough Memory!\n"); free(p); getchar(); return 0; }
函数名: calloc
功 能: 分配主存储器
原型:extern void *calloc(size_t num,size_t size));
用法:#include<stdlib.h>
功能:分配长度为num*size个字节的内存块
说明:如果分配成功则返回指向被分配内存的指针,否则返回空指针NULL。
当内存不再使用时,应使用free()函数将内存块释放。
程序例:
#include <stdio.h> #include <stdlib.h> int main(void) { char *str = NULL; /* allocate memory for string */ str = calloc(100, sizeof(char)); if(str) //判断是否为空 printf("Memory Allocated at: %x", str); else printf("NotEnough Memory!\n"); /* copy "Hello" into string */ strcpy(str, "Hello"); printf("String is %s\n", str); free(str); return 0; }
函数名: realloc
原型:extern void *realloc(void*mem_address, unsigned int newsize);
用法:#include <stdlib.h>
功能:改变mem_address所指内存区域的大小为newsize长度。
说明:如果重新分配成功则返回指向被分配内存的指针,否则返回空指针NULL。
当内存不再使用时,应使用free()函数将内存块释放。
举例:
// realloc.c
#include<stdio.h> #include <stdlib.h> int main() { char *p; p=(char *)malloc(100); if(p) //判断是否为空 printf("Memory Allocated at: %x",p); else printf("NotEnough Memory!\n"); getchar(); p=(char *)realloc(p,256); if(p) //判断是否为空 printf("Memory Reallocated at: %x",p); else printf("Not Enough Memory!\n"); free(p); getchar(); return 0; }
(一) 用函数指针变量调用函数
可以用指针变量指向整形变量、字符串、数组、结构体、也可以指向一个函数。一个函数在编译时被分配一个入口地址。这个入口地址就称为函数指针。可以用一个指针变量指向函数,然后通过该指针变量调用此函数。用简单的数值比较为例:
#include <stdio.h> #include <stdlib.h> int main() { int max(int,int); int (*p)(int,int); int a,b,c; p = max; scanf("%d,%d",&a,&b); c = (*p)(a,b); printf("a=%d,b=%d,max=%d\n",a,b,c); return 0; } int max(int x,int y) { int z; if(x>y) z = x; else z = y; return(z); }
第7行:int (*p)( int,int ); 用来定义 p 是一个指向函数的指针变量,该函数有两个整形参数,函数值为整形。
赋值语句 p = max ; 作用是将函数 max 的入口地址赋给指针变量p。和数组名代表数组首元素地址类似,函数名代表该函数的入口地址。这时 p 就是指向函数 max 的指针变量,此时 p 和 max都指向函数开头,调用 *p 就是调用 max 函数。但是p作为指向函数的指针变量,它只能指向函数入口处而不可能指向函数中间的某一处指令处,因此不能用 *(p + 1)来表示指向下一条指令。
注意:
(1) 指向函数的指针变量的一般定义形式为:
数据类型 (*指针变量名)(函数参数列表)
这里数据类型就是函数返回值的类型
(2) int (* p) ( int,int ); 它只是定义一个指向函数的指针变量 p, 它不是固定指向哪一个函数的,而只是表示定义这样一个类型的变量,它是专门用来存放函数的入口地址的。在程序中把哪一函数(该函数的值应该是整形的,且有两个整形参数)的地址赋给它,他就指向哪一个函数。在一个函数中,一个函数指针变量可以先后指向同类型的不同函数。
(3) p = max; 在给函数指针变量赋值时,只需给出函数名而不必给出函数参数,因为是将函数的入口地址赋给 p ,而不涉及 实参和形参的结合问题,不能写成 p = max(a,b);
(4) c = (*p)(a,b) 在函数调用时,只需将( *p ) 代替函数名即可,后面实参依旧。
(5) 对于指向函数的指针变量,像 p++ ,p+n.....是无意义的。
(二) 用指向函数的指针作为函数参数
函数指针变量通常的用途之一就是把指针作为参数传递到其他函数。
函数的参数可以是变量、指向变量的指针变量、数组名、指向数组的指针变量,也可以是指向函数的指针也可以作为参数,以实现函数地址的传递,这样就能够在被调用的函数中使用实参函数。
void sub ( int ( *x1) (int), int (*x2) (int,int) )
{
int a,b,i,j;
a = (*x1)(i); /* 调用 f1 函数 */
b = (*x2)(i)(j); /* 调用 f2 函数 */
}
如果实参为两个 函数名 f1 和 f2. 在函数首部定义x1、x2为函数指针变量,x1指向的函数有一个整形形参,x2指向的函数有两个形参。i 和 j 是函数f1 和 f2所要的参数。函数sub的形参 x1、x2(指针变量)在函数 sub 未被调用时并不占用内存单元,也不指向任何函数。在sub被调用时,把实参函数 f1 和 f2的入口地址传给形式指针变量 x1 和 x2.
既然在 sub 函数中要调用 f1 和 f2 函数,为什么不直接调用f1 和 f2而要用函数指针变量呢? 确实,如果只是用到f1 和 f2 函数,完全可以在sub函数中直接调用f1 和 f2,而不必设指针变量 x1 和 x2。 但是,如果在每次调用sub时,调用的函数不是固定的,下次是f3 和 f4,再是f5 和 f6...这时用指针变量就比较方便了。
返回指针的函数
每个函数可返回一个值,返回值可以是char、int、float、double等类型,当将返回值类型设置为void时,表示函数没有返回值。在C语言中,还允许一个函数的返回值是一个指针(即地址),这种返回指针的函数称为指针型函数。
定义指针型函数的形式如下:
- 类型说明符 *函数名(形参表)
- {
- … /*函数体*/
- }
其中函数名之前加了"*"号表明这是一个指针型函数,即返回值是一个指针。类型说明符表示了返回的指针值所指向的数据类型。一般用这种函数返回一个字符串常量的首地址。
编写一个函数,用于将阿拉伯数字表示的月份转换为对应的英文名称。函数一次只能返回一个值,若要返回一个字符串(由多个字符组成),用前面已介绍的方法可通过函数的形参返回多个字(包括一个字符串)。例如,用以下的函数头:
- void cmonth(int month, char s[])
要调用以上形式的函数,首先要定义一个数组,再将数组作为实参传给函数,最后将函数处理的结果用另一个语句输出。使用类似下面的程序:
- char s[20];
- cmonth(5, s]);
- printf("月份:%2d-->英文名称:%s\n",5,s);
如果函数能返回字符串,则可以使用以下方式调用函数,并输出返回值:
- printf("月份:%2d-->英文名称:%s\n",i,cmonth(i));
编写指针型函数可返回字符串的首地址,下面的程序演示指针型函数的编写方法。
【程序9-27】
- #include <stdio.h> //头文件
- #include <stdlib.h>
- char *cmonth(int month);//函数声明
- int main()
- {
- int i;
- printf("输入月份数字:");
- scanf("%d",&i); //输入月份
- printf("月份:%2d-->英文名称:%s\n",i,cmonth(i));
- system("pause");
- return 0;
- }
- char *cmonth(int month)//自定义函数
- {
- char *str_month[]={//初始化
- "Illegal Month",
- "January",
- "February",
- "March",
- "April",
- "May",
- "June",
- "July",
- "August",
- "September",
- "October",
- "November",
- "December"
- };
- char *p;
- if(month>=1 && month<=12) //判断是否合法
- p=str_month[month];
- else
- p=str_month[0];
- return p;
- }
执行这段程序,按照提示输入月份数字,得到如下结果,如图9-53所示。
在该程序中,定义了函数cmonth(),该函数需要一个整型变量作为实参,返回一个字符型指针。在函数体内部定义指针数组,数组中的每个指针指向一个字符串常量。然后,判断实参month是否合法,若不合法则将第一个元素赋值给字符指针变量p,这样,指针变量p中的值就与指针数组中第一个元素中的值相同,即指向字符串常量"Illegal Month",如图9-54所示。当函数参数month为1~12之间的一个值时,即可使字符指针指向对应的字符串常量(变量p中保值的值是一个地址)。
main()函数中,在printf()函数输出列表中包括cmonth()函数的返回值(其返回值是一个字符串的首地址),printf()函数的格式字符"%s"从该首地址开始输出字符串。
![]() |
图9-53 执行结果 |
![]() |
图9-54 用指针操作字符串 |
指针与数组
1 通过指针引用数组元素
int a[10];
int *p;
p=a;(p=&a[0])
P+i , a+i 都是&a[i];
*(P+i) ,*( a+i)都是a[i];
p[i]=*(p+i)=a[i];
例子:求给定10个整型数中的最大值
#include<stdio.h> int main() {int a[10]={5,3,6,1,7,4,8,2,19,100}; int i,max,*p; p=a; max=*p; for(i=1;i<=10;i++,p++) {if(*p>max)max=*p; } printf("max=%d\n",max); return 0; }
常规用法:
main()
{ int a[10];
......
......
f(a,10);
......
......
}
f(int array[], int n)
{
......
......
}
上述:实际上是将数组的首地址传给形参,这样实参数组与形参数组共占同一段内存空间。
因而也可以使用指针变量代替数组名来进行地址的传送。
还可以有以下几种对应关系:
1 实参用数组名,形参用指针变量:
int a[10];
f(a, 10);
f( int *p, int n)
{
......
......
}
2 实参用指针变量,形参用数组名;
int a[10], *pa;
pa=a;
......
f(pa, 10);
......
f(int b[], int n)
{
......
}
3 形参和实参都用指针变量
int a[10], *pa;
pa = a;
....
f(pa, 10);
.....
f(int *p, int n)
{
......
}
例子:用指针对数组进行从小到大排序
#include<stdio.h> sort(int *p,int n) {int i,j,temp; for(i=0;i<n-1;i++) for(j=i+1;j<n;j++) if(*(p+i)>*(p+j)) {temp=p[i]; *(p+i)=*(p+j); *(p+j)=temp; } return 0; } int main() {int a[10],*pa,i; pa=a; printf("Input 10 integer:"); for(i=0;i<10;i++) scanf("%d",pa++); pa=a; sort(pa,10); for(i=0;i<10;i++) printf("%d ",*pa++); return 0; }
3 多维数组与指针
例如:
int a[3][4]={{0,1, 2, 3},{10, 11, 12, 13},{20, 21, 22, 23}};
a[i]+j 是&a[i][j];
*(a[i]+j) 是 a[i][j];a+i可以看做是指向a[i]的指针
*(a+i)=a[i];(依然是地址值,即a[i][0]的地址,一维数组)
*(*(a+i) + j)=a[i][j];
用指向数组元素的指针变量引用数组元素
#include<stdio.h> int main() {int a[3][4]={{0,1,2,3},{10,11,12,13},{20,21,22,23}}; int *p,i,j; p=a[0]; printf("\n"); for(i=0;i<3;i++) { for(j=0;j<4;j++) printf("%4d",*p++); printf("\n"); } return 0; }
4 指针与字符串
#include<stdio.h> int main() {char *ps="How do you do!"; printf("%s\n",ps); return 0; }