备注: 凡是在《程序员面试宝典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
#include
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
#include
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
#include
using namespace std;
int main()
{
cout<<"Enterthe numbers "<
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
示例如下:
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
#include
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
功能:分配长度为num_bytes字节的内存块
说明:如果分配成功则返回指向被分配内存的指针,否则返回空指针NULL。
当内存不再使用时,应使用free()函数将内存块释放。
举例:
// malloc.c
#include
#include
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
功能:分配长度为num*size个字节的内存块
说明:如果分配成功则返回指向被分配内存的指针,否则返回空指针NULL。
当内存不再使用时,应使用free()函数将内存块释放。
程序例:
#include
#include
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
功能:改变mem_address所指内存区域的大小为newsize长度。
说明:如果重新分配成功则返回指向被分配内存的指针,否则返回空指针NULL。
当内存不再使用时,应使用free()函数将内存块释放。
举例:
// realloc.c
#include
#include
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#include 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语言中,还允许一个函数的返回值是一个指针(即地址),这种返回指针的函数称为指针型函数。
定义指针型函数的形式如下:
其中函数名之前加了"*"号表明这是一个指针型函数,即返回值是一个指针。类型说明符表示了返回的指针值所指向的数据类型。一般用这种函数返回一个字符串常量的首地址。
编写一个函数,用于将阿拉伯数字表示的月份转换为对应的英文名称。函数一次只能返回一个值,若要返回一个字符串(由多个字符组成),用前面已介绍的方法可通过函数的形参返回多个字(包括一个字符串)。例如,用以下的函数头:
要调用以上形式的函数,首先要定义一个数组,再将数组作为实参传给函数,最后将函数处理的结果用另一个语句输出。使用类似下面的程序:
如果函数能返回字符串,则可以使用以下方式调用函数,并输出返回值:
编写指针型函数可返回字符串的首地址,下面的程序演示指针型函数的编写方法。
【程序9-27】
执行这段程序,按照提示输入月份数字,得到如下结果,如图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
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
sort(int *p,int n)
{int i,j,temp;
for(i=0;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
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
int main()
{char *ps="How do you do!";
printf("%s\n",ps);
return 0;
}