10 变量
变量是一段有名字的连续存储空间,它是程序中数据的临时存放场所。
10.1 全局变量和静态变量有什么异同?
相同:都在静态存储区分配空间,生命周期与程序生命周期相同。
区别:全局变量的作用域是整个程序,它只需要在一个源文件中定义,就可以作用于所有的源文件。而静态变量只在定义其的源文件内有效。
10.2 变量定义与变量声明有什么区别?
定义(definition) 为变量分配存储空间,还可以为变量指定初始值。而声明(declaration)是指向程序表明变量的类型和名字。 定义也是声明,定义变量的同时也声明了它的类型和名字。可以通过使用extern关键字声明变量名而不定义它,它所说明的并非自身,而是描述其他地方创建的对象,可以多次出现, 如 extern int my_array[]。
“定义也是声明”,这说明定义包括声明,对于int a来说,它既是定义又是声明,对于 extern int a来说,它是声明不是定义。一般为了叙述方便,把建立存储空间的声明称定义,而 不把建立存储空间的声明称为声明。
10.3 不使用第三方变量,如何交换两个变量的值?
#include <iostream>
using namespace std;
#define swap1(x,y) x=x+y,y=x-y,x=x-y
#define swap2(x,y) x^=y,y^=x,x^=y
int main()
{
int a=1,b=2;
//方法1 - 算数法
swap1(a,b);
cout << "a=" << a << " b=" << b << endl;
//方法2 - 异或法
a=1,b=2;
swap2(a,b);
cout << "a=" << a << " b=" << b << endl;
}
10.4 C语言中各种变量的默认初始值是什么?
全局变量放在内存的全局数据区,如果在定义的时候不初始化,则系统将自动为其初始化,数值型为0,字符型为NULL,即0,指针变量也被赋值为NULL。静态变量的情况与全局变量类似。而非静态局部变量如果不显示初始化,那么其内容是不可预料的,将是随机数,会很危险,对系统的安全造成非常大的隐患。
11 字符
字符串是编程语言的基础,对于字符串的处理一直是程序设计的重要组成部分,所以在面试笔试中,经常会考查字符串处理函数的机理。掌握常见的字符串处理函数原理,并能自己实现其原型,对于应对程序员面试笔试非常重要。
11.1 不使用C/C++字符串库函数,如何自行编写strcpy()函数?
char * strcpy(char *strDest, const char *strSrc)
{
assert((strDest!=NULL) && (strSrc!=NULL));
char *address = strDest;
while( (*strDest++ = *strSrc++) != '\0');
return address ;
}
返回类型为char *主要是为了实现链式表达式。例如:
int length=strlen(strcpy(strDest, "hello world"));
strcpy(strDest, strcpy(strDest1, strSrc));
可以将strSrc复制到strDest1与strDest中,也就是说,可以将函数的返回值做为另一个函数的参数。
11.2 如何把数字转换成字符串?
C语言中常用到字符串与数字之间的相互转换,常见的此类库函数有atof (字符串转换成浮点数)、atoi (字符串转换成整型数)、atol (字符串转换成长整型数)、itoa (整型数转换成字符串)、ltoa (长整型数转换为字符串)等。
为了考查求职者对基本功的掌握,在程序员的面试笔试中仍然经常会见到此类题目,让求职者自定义此类函数的实现,此类题目虽然难度不大,但是需要认真仔细对待。
以自定义Myatoi()与Myitoa()函数为例,分别实现自定义字符串转换为整型数函数与自定义整型数转换为字符串函数。以下为自定义Myatoi()函数的实现以及测试代码:
#include<stdio.h>
int Myatoi(char *str)
{
if(str = NULL)
{
printf("Invalid Input");
return -1;
}
while(*str = '') //跳过开头的空格字符
{
str++;
}
//'0xA1'是不打印字符,一般是占两个字节的汉字
while((*str==(char)0xA1) && (*(str+1)==(char)0xAA))
{
str += 2;
}
int nSign = (*str = '-')?-1:1;//确定符号位
if(*str='+' || *str='-')
{
*str++;
}
int nResult = 0;
while(*str>='0' && *str<='9')
{
nResult = nResult*10 + (*str-0);
*str++;
}
return nResult * nSign;
}
int main()
{
printf(""%d\n",Myatoi("12345"));
return 0;
}
程序输出结果:
12345
以下为自定义Myitoa函数的实现以及测试代码:
#include <stdio.h>
char *Myitoa(int num)
{
char str[1024];
int sign = num,i = 0,j = 0;
char temp[11];
//如果为负数,则转换为其绝对值
if(sign<0)
{
num = -num;
};
//数字转换为倒序的字符数组
do
{
temp[i] = num%10 + '0';
num/=10;
i++;
}while(num>0);
//字符数组加上“符号”
if(sign<0)
{
temp[i++] = '-';
}
//转换为字符串
temp[i] = '\0';
//将字符串反转
i—-;
while(i>=0)
{
str[j] = temp[i];
j++;
i--;
}
str[j] ='\0';
return str;
}
int main()
{
printf("%s\n",Myitoa(-12345));
return 0;
}
程序输出结果:
12345
11.3 如何自定义内存复制函数memcpy()?
memcpy是C语言中的内存复制函数,它的函数原型为void memcpy(void dest, const void*src, size_t n)。它的目的是将src指向地址为起始地址的连续n个字节的数据复制到以dest指向地址为起始地址的空间内,函数返回指向destin的指针。需要注意的是,src和dest所指内存区域不能重叠,同时,与strcpy相比,memcpy遇到’\0’不结束,而是一定会复制完n个字节。
自定义内存复制函数示例如下:
#include <stdio.h>
void *MyMemCpy(void *dest, const void *src, size_t count)
{
char *pdest = static_cast<char*>(dest);
const char *psrc = static_cast<const char*>(src);
if((pdest>psrc) && (pdest<(psrc+count))) //这种情况:MyMemCpy(str+1, str+0, 9);
{
for(size_t i=count-l; i!=-l; —-i)
pdest[i] = psrc[i];
}
else
{
for(size_t i=0; i<count; ++i) //这种情况:MyMemCpy(str, str+5, 5);
pdest[i] = psrc[i];
}
return dest;
}
int main()
{
char str[] = "0123456789";
MyMemCpy(str+1, str+0, 9);
printf("%s\n",str);
MyMemCpy(str, str+5, 5);
printf("%s\n",str);
return 0;
}
12 编译
编译是程序执行过程中的一个重要步骤,了解程序的编译过程以及编译方式都对程序的开发起着至关重要的作用。通过对编译过程的理解,有助于程序员编写逻辑清晰、髙效的代码, 有助于减少程序中存在的潜在Bug。
12.1 编译和链接的区別是什么?
在多道程序环境中,要想将用户源代码变成一个可以在内存中执行的程序,通常分为3个步骤:编译、链接、载入。
(1) 编译:将预处理生成的文件,经过词法分析、语法分析、语义分析以及优化后编译成若干个目标模块。可以理解为将高级语言翻译为计算机可以理解的二进制代码,即机器语言。
(2) 链接:由链接程序将编译后形成的一组目标模块以及它们所需要的库函数链接在一起,形成一个完整的载入模型。链接主要解决模块间的相互引用问题,分为地址和空间分配, 符号解析和重定位几个步骤。 链接一般分为静态链接、载入时动态链接以及运行时动态链接3种。
(3) 载入:由载入程序将载入模块载入内存。
编译和链接是为将用户程序从硬盘上调入内存并将其转换成可执行程序服务的。以C/C++语言为例,把源文件编译成中间代码文件,在windows下面为.obj文件,在 UNIX、Linux F面就是.o文件,即Object File,该动作被称为编译。然后再把大量的object File合成可执行文件,这个动作称为链接。
编译时,编译器需要的是语法正确,函数与变量的声明正确。而一般来说,每个源文件都应该对应于一个中间目标文件(.o文件或是.obj文件)。链接时,主要是链接函数和全局变量, 所以可以使用这些中间目标文件(.o文件或是.obj文件)来链接应用程序。链接就是那些目标文件之间相互链接自己所需要的函数和全局变量,而函数可能来源于其他目标文件或库文件。
12.2 如何判断一段程序是C编译程序还是C++编译程序编译的?
如果编译器在编译cpp文件,那么_cplusplus就会被定义,如果是一个C文件在被编译, 那么_STDC_就会被定义。_STDC_是预定义宏,当它被定义后,编译器将按照ANSIC标准来编译C语言程序。
所以,可以采用如下程序示例判断:
#ifdef _cplusplus
#define USING_C 0
#else
#define USING_C 1
#endif
#include <stdio.h>
int main()
{
if(USING_C)
printf("C\n");
else
printf("C++\n");
return 0;
}
12.3 C++程序中调用被C编译器编译后的函数,为什么要加extern "C"?
C++语言是一种面向对象编程语言,支持函数重载,而C语言是面向过程的编程语言, 不支持函数重载,所以函数被C++编译后在库中的名字与C语言的不同。如果声明一个C语 言函数float f(int a,char b), C++的编译器就会将这个名字变成像_f_int_char之类的东西以支持函数重载。然而C语言编译器的库一般不执行该转换,所以它的内部名为_f,这样连接器将无法解释C++对函数f()的调用。
C++提供了 C语言 (替代连接说明)符号extern “C”来解决名字匹配问题,extern后跟一个字符串来指定想声明的函数的连接类型,后面是函数声明。
extern "C" float f(int a,char b);
该语句的目的是告诉编译器f()是C连接的,这样C++就不会转换函数名。应该到库中找名字_f而不是找_f_int_char。 C++编译器开发商已经对C标准库的头文件作了extern “C”处理,所以可以用include直接引用这些头文件。
12.4 两段代码共存于一个文件,编译时有选择地编译其中的一部分,如何实现?
可以通过以下两种方法实现:
(1) 在源码中使用条件编译语句,然后在程序文件中定义宏的形式来选择需要的编译代码。
(2) 在源码中使用条件编译语句,然后在编译命令的命令中加入宏定义命令来实现选择编译。