《C语言深度解剖》附录解析,附测试代码
前言
即将面对各种招聘会场,即将迎来各种面试题目,师兄推荐我一本书《C语言深度解剖》买来看看。书的前言部分说明:在开始学习本书之前做一下附录1的C语言基础测试题并根据分值评分,100分以上可以把这本书扔了,80分以上说明C语言基础还不错,50分一下不要气馁,努力学习本书,结果我做了一边后,得分不说了,好好看书吧! <_>但在看完解析后,确实收益匪浅,拉出来大家探讨探讨。 相信对这篇博客感兴趣的你肯定手上有书或者电子版,那如果是无意翻到这篇博客,想看看这本书,在我的资源里可以免费下载本书的pdf电子版。附录部分的题目我就不例举了。另外,测试代码的测试环境为Visual Studio 2012
1.
知识点:
隐式转换
1.算术运算:低类型转换为高类型
2.赋值表达式:表达式的值转换为左边变量的类型
3.函数调用:实参转换为形参的类型
4.函数返回值:return表达式转换为返回值类型
测试代码:
#include <stdio.h>
void main(void)
{
unsigned int i=6;
int j=-20;
(i+j>6)? printf(">6\r\n",j):printf("<6\r\n");//输出>6
printf("i+j:%d\r\n",i+j);//i+j:输出-14
getchar();
}
看到这很多人就开始挠头了,“-14”为什么大于"6"???懵了?我们来根据上面的知识点梳理一下。有符号-20会转换为无符号0xffffffec(补码),一个很大的值,这样看i+j肯定>6。那后面为什么输出为"i+j:输出-14"。这就牵扯到printf()函数的转换说明,这里不细讲,主要是%d:有符号十进制,%u:无符号十进制,%x:无符号十六进制。在上面的代码中加入下面补充,你肯定理解的透透的了。
printf("i+j:%u\r\n",j+i);//无符号十进制
printf("i+j:%x\r\n",j+i);//无符号十六进制
(i+j==0xfffffff2)? printf("我很帅\r\n",j):printf("你很帅\r\n");//判断语句
2.
知识点:
字符数组
C 语言规定以字符’\0’作为字符串结束标志。例如
char c[ ]={‘F’,’e’,’i’,’L’,’o’,’n’,’g’};//字符数组中没有’\0’表示是普通的字符数组
char c[ ]= {‘F’,’e’,’i’,’L’,’o’,’n’,’g’,’\0’};//字符数组以字符’\0’结束表示是字符串
char c[ ]=“FeiLong”;//使用字符串方式进行赋值时,系统会自动在字符串末尾添加’\0’
printf 等函数处理字符串的时候通常都是“不见\0 不死心”,因此使用字符串的时候一定不要忘了结尾的\0。
strcpy()函数
strcpy,即string copy(字符串复制)的缩写。
原型声明:char strcpy(char dest, const char *src);
头文件:#include <string.h> 和 #include <stdio.h>
功能:把从src地址开始且含有NULL结束符的字符串复制到以dest开始的地址空间。注意strcpy和printf 一样,处理字符串的时候通常都是“不见\0 不死心”。
测试代码:
#include <stdio.h>
#include <string.h>
void main(void)
{
char string[10],str1[10];
int i;
for(i=0;i<10;i++)
{
str1[i]='a';
}
//str1[9]='\0';//添加结束符
strcpy(string,str1);//strcpy遇到‘\0’结束,str1无结束标志
printf("%s",string);//printf打印遇到‘\0’结束
getchar();
}
如果不强制添加\0,strcpy()会一直copy,程序出错。添加代码中注释的部分,正常输出。
3.
知识点:
static的作用
1). 在函数体,一个被声明为静态的变量在这一函数被调用过程中维持其值不变。
2). 在模块内(但在函数体外),一个被声明为静态的变量可以被模块内所用函数访问,但不能被模块外其它函数访问。它是一个本地的全局变量。
3). 在模块内,一个被声明为静态的函数只可被这一模块内的其它函数调用。那就是,这个函数被限制在声明它的模块的本地范围内使用。可以通过下面代码进行测试。
#include <stdio.h>
#include <string.h>
void fun1(void)
{
static int i=0;
i++;
printf("i value:%d\r\n",i);
printf("i address:%p\r\n",&i);
}
void fun2(void)
{
int j=0;
j++;
printf("j value:%d\r\n",j);
printf("j address:%p\r\n",&j);
}
void main(void)
{
int k;
for(k=0;k<10;k++)
{
fun1();
fun2();
}
getchar();
}
4.
知识点:
sizeof求字节大小
32位系统下
32位系统下基本的类型对应的字节数为下表:
类型 | 字节数 | 类型 | 字节数 |
---|---|---|---|
char | 1 | int | 4 |
short | 2 | uint | 4 |
long | 4 | char* | 4 |
ulong | 4 | int * | 4 |
long long | 8 | int** | 4 |
int *p=NULL;
sizeof(p);//指针大小,4字节
sizeof(*p);//int类型变量,4字节
int a[100];
sizeof(a);//sizeof(数组名)表示数组的大小,4*100=400字节
sizeof(a[100]);//数组元素,int类型变量,4字节
sizeof(&a);//数组首元素地址,4字节
sizeof(&a[0]);//数组首元素地址,4字节
int b[100];
void fun(int b[100])
{
sizeof(b);//传入的是b的指针,代表b首元素地址,4字节
}
5.
知识点:
strlen()函数
strlen()函数:strlen()相当于一个计数器,它从内存的某个位置(可以是字符串开头,中间某个位置,甚至是某个不确定的内存区域)开始扫描,直到碰到第一个字符串结束符’\0’为止,然后返回计数器值(长度不包含’\0’)。
sizeof()函数:sizeof() 是一种内存容量度量函数,功能是返回一个变量或者类型的大小(以字节为单位);在 C 语言中,sizeof() 是一个判断数据类型或者表达式长度的运算符。
char c[]="FeiLong";
printf("%d\r\n",strlen(c));//7
printf("%d\r\n",sizeof(c));//8
结合下面的测试代码的输出和附录2中的解析,应该很好懂
#include <stdio.h>
#include <string.h>
void main(void)
{
char a[1000];
int i;
for(i=0;i<1000;i++)
{
a[i]= -1-i;
printf("%d:%c\r\n",i,a[i]);
}
printf("%d\r\n",strlen(a));
getchar();
}
6.
知识点:
常量指针和指向常量的指针
代码 | 备注 | 可改 | 不可改 |
---|---|---|---|
const int *p | 指向常量的指针 | p | *p |
int const *p | 指向常量的指针 | p | *p |
int * const p | 常量指针 | *p | p |
const int * const p | 指向常量的常量指针 | p *p |
7.
知识点:
volatile
一个定义为 volatile 的变量是说这变量可能会被意想不到地改变,这样,编译器就不会
去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地从内存重新读取这个变量的值,而不是使用保存在寄存器里的备份。下面是 volatile 变量的几个例子:
1). 并行设备的硬件寄存器(如:状态寄存器)
2). 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
3). 多线程应用中被几个任务共享的变量
结合附录2的解析,还好理解。曾经做过一个项目,一个数据就是因为没加volatile而不能正确读取,当时纠结了好一阵。
8.
知识点:
数组与指针
跑一段测试代码,就可以明白了,解析在注释中。
#include <stdio.h>
#include <string.h>
void main(void)
{
int a[5]={1,2,3,4,5};
int *ptr1=(int *)(&a+1);
int *ptr2=(int *)(int)a+1;
//测试a、&a[0],&a为同一地址,数组首元素的地址
printf("a addr:%p\r\n",a);
printf("&a[0] addr:%p\r\n",&a[0]);
printf("&a addr:%p\r\n",&a);
//测试a+1和&a+1的不同
printf("a+1 addr:%p\r\n",a+1);//移动一个数组元素类型大小的字节
printf("&a+1 addr:%p\r\n",&a+1);//移动一个数组大小的字节
//&a+1与数组元素的对应关系
printf("*(*(&a+1)-1) value:%d\r\n",*(*(&a+1)-1));//*(*(&a+1)-1)=a[4]
printf("ptr1[-1] value:%d\r\n",ptr1[-1]);//ptr1[-1]=a[4]
printf("\r\n\r\n ptr1[-1]:%x\r\n *ptr2:%x\r\n",ptr1[-1],*ptr2);
getchar();
}
附录2中的解析答案为5,2000000.
我的答案为5,2.我直接程序测试的,所以没去搜此书的勘误。
9.
知识点:
运算符优先级
这个,嗯…嗯…嗯…百度吧
运算符优先级(百度百科)
10.
知识点:
宏定义函数
1.注意宏定义之间的空格
#define f (x) ((x)-1)
此语句等价于
#define f (x)((x)-1)
2.注意宏中的括号(直接替换,宏不是函数)
#define abs(x) ( ((x)>=0)? (x):-(x) )//正确
#define abs(x) x>0?x:-x //错误
按照后一种执行
abs(a-b)
a-b>0 ? a-b:-a-b//此时-a-b相当于(-a)-b,与原意不符
abs(a)+1
abs(a-b)
a>0 ? a:-a+1//此时后面为-a+1,与原意不符
11.
知识点:
结构体字节数
1.熟悉4.中的数据类型对应的字节大小(不熟悉的再看看32位系统下的数据)
2.结构体字节对齐(你至少得知道下面的两点)
1)结构体元素放置的位置一定是在自己宽度的整数倍上开始
2)计算出的存储单元为所有元素中最宽的元素的长度的整数倍
struct Test
{
char a;
short b;
char c;
int d;
}
根据上面的知识点,我们走一遍。
首先char为1个字节 (1)。此时为1
short为2个字节 (1),b要以2字节的倍数处开始 (2. 1) ),即2处开始。此时为4
char为1个字节 (1)。此时为5
int为4个字节 (1),db要以4字节的倍数处开始 (2. 1) ),即8处开始。此时为12
验证 (2. 2) ),最宽的元素的长度为int,即4。最终的12满足4的倍数,结果为12.
12.
知识点:
内存地址写值
注意在地址前加入相应的转换类型
char *p=(char *)0x123456;
int *p=(int *)0x123456;
13.
知识点:
数组与指针
类似8题。
14.
知识点:
结构体大小与数据类型
走一波测试代码:
#include <stdio.h>
#include <string.h>
struct Test
{
int Num;//4
char *pcName;//8
short sDate;//10
char a[2];//12
short b[4];//20
}*p;
void main(void)
{
printf("sizeof(struct Test):%d\r\n",sizeof(struct Test));
printf("p addr:%p\r\n",p);
printf("p+1 addr:%p\r\n",p+1);//加一个结构体的值
printf("(unsigned long)p+1 addr:%p\r\n",(unsigned long)p+1);//将p转换为数值,再加1
printf("(unsigned int*)p+1 addr:%p\r\n",(unsigned int *)p+1);//将p转化为指针,加1为指针的大小:4字节
getchar();
}
结构体大小:代码中根据11中的方法标记了结构体吧相应的字节大小值,不太懂的就建立个结构体,输出结构体大小。不断更改结构体中的数据,看结果分析。
数据类型:看代码注释。
15.
知识点:
二维数组初始化前两天刚写过,这题我竟然觉得考点是二维数组赋值与转换关系,啪啪打脸。。。
数组与逗号表达式
逗号表达式
1.从左到右逐个计算
2.逗号表达式作为一个整体,它的值为最后一个表达式的值
3.逗号表达式的优先级在所有运算中最低
上测试代码:
#include <stdio.h>
#include <string.h>
void main(void)
{
int i,j;
int *p;
//int a[3][2]={{0,1},{2,3},{4,5}};//二维数组赋值,注意是{}
int a[3][2]={(0,1),(2,3),(4,5)};//下面的输出为1,3,5,0,0,0
//int a[3][2]={(0,1,4),(2,3,6),(0,4,5)};//下面的输出为4,6,5,0,0,0
//测试两者的不同
for(i=0;i<3;i++)
{
for(j=0;j<2;j++)
{
printf("a[%d][%d]:%d\r\n",i,j,a[i][j]);
}
}
p=a[0];
printf("%d",p[0]);
getchar();
}
16.
知识点:
数组与指针
函数参数不仅可以是变量,也可以是数组,它的作用是将数组首元素地址传给函数形参。在 C 语言中,数组做函数参数时,是没有副本机制的,只能传递地址。也可以认为,数组做函数参数时,会退化为指针。
测试代码:
#include <stdio.h>
#include <string.h>
void test1(char a[10])
{
printf("test1 value:%d\r\n",sizeof(a));
}
void test2(char *a)
{
printf("test2 value:%d\r\n",sizeof(a));
}
void fun(char a[10])
{
char c=a[3];
printf("c value:%c",c);
}
void main(void)
{
char string[10]="abcdefg";
test1(string);
test2(string);
printf("string[9] value:%p\r\n",string[9]);
printf("string[10] value:%p\r\n",string[10]);
printf("string[9] addr:%p\r\n",&string[9]);
printf("string[10] addr:%p\r\n",&string[10]);
//fun[&string[10]];//void fun(char *)
fun(string);//可正常取值
getchar();
}
代码中的test1和test2验证了知识点,本题错误看附录2解析,string[10]地址对应string[9]的下一个,但string[10]已越界,其中存的值不可预估。函数参数中传输的是地址,若想实现功能,如代码所示fun(string);。
17.
知识点:
栈和指针
栈和指针是个不错的点,简单的自我理解总结一下:
局部变量自动分配栈上内存,跳出此函数时自动释放,栈的大小有限,超出容易溢出。
堆内存相对无限制(虚拟内存),不过容易忘记释放导致内存溢出,频繁的动态分配内存容易导致内存碎片。
总的来讲,明确了数据大小,可用栈(数组)。不知数据大小,用堆(动态分配)。
本体采用堆上动态分配内存,但name指针没有分配内存。
测试代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct student
{
char *name;
int score;
}stu,*pstu;
void main(void)
{
pstu=(struct student *)malloc(sizeof(struct student));
//pstu ->name=(char *)malloc(sizeof(char)*10);
strcpy(pstu ->name ,"FL1022");
pstu ->score =99;
printf("name:%s",pstu ->name);
free(pstu);
getchar();
}
实现功能语句为代码注释部分。
18.
知识点:
递归
#include <stdio.h>
#include <string.h>
void fun(int i)
{
if(i>0)
{
fun(i/2);
}
printf("%d",i);
}
void main(void)
{
fun(10);
getchar();
}
此题我的结果和附录2解析答案不一致,最好的验证方法就是代码验证,输出结果和我的一致,就不看勘误了。
19.
知识点:
getchar()
int getchar(void);
返回值应为int型。
20.
知识点:
大小端与union
大端:高字节到低字节
小端:低字节到高字节
例如16bit环境,内存地址为0x1000,数据为1234
大端
地址 | 存储值 |
---|---|
0x1000 | 0x12 |
0x1001 | 0x34 |
小端
地址 | 存储值 |
---|---|
0x1000 | 0x34 |
0x1001 | 0x12 |
联合体union的存放顺序是所有成员都从低地址开始存放。
结合附录2中的程序,悟一下。
一下午+一晚上。希望你有所收获!