《C语言深度解剖》(第3版)的读书笔记

参考链接:
https://www.cnblogs.com/huangdengtao/p/11450539.html
C语言深度剖析 - 博客园找找看
https://www.cnblogs.com/xkfz007/archive/2012/02/27/2369562.html

第 1 章 关键字

数据类型与字节大小


字节参考

  • 所有指针占用内存是相同的

    • sizeof(int *) = sizeof(char *) = sizeof(void *)
  • 32 位和 64 位的区别有 char *long、unsigned long。

  • 32位系统:

数据类型 字节大小
short 2
int 4
int * 4,32位/8=4B
unsigned int 4
char 1
char * 4
float 4
double 8
long 4
unsigned long 4
long long 8
  • 64位系统:
数据类型 字节大小
short 2
int 4
int * 8,64位/8=8B
unsigned int 4
char 1
char * 8
float 4
double 8
long 8
unsigned long 8
long long 8
  • signed int 范围:-2^31 ~ 2^31-1
  • unsigned int 范围:0 ~ 2^32-1
  • char 范围:-2^7 ~ 2^7-1
  • signed char 范围:-128 ~ 127
  • unsigned char 范围:0 ~ 255
  • unsigned int == unsigned,int 可省略
  • signed int == int,signed 可省略

printf() 函数常用格式控制字符


格式字符 说明
d 以十进制形式输出带符号整数(整数前不输出符号 +)
o 以八进制形式输出无符号整数(前缀 0 不输出)
x 或 X 以十六进制形式输出无符号整数(前缀 0x 不输出)
u 以十进制形式输出无符号整数
c 以字符形式输出单个字符
s 以字符串形式输出字符串
f 以小数形式输出单、双精度实数,系统默认输出六位小数
e 或 E 以标准指数形式输出单、双精度实数
g 或 G 以 %f 或 %e 格式中输出宽度较短的一种格式输出单、双精度实数
p 以十六进制整数方式输出指针的值(前缀 0x 不输出),位数不够时,左边补 0

printf() 函数常用附加格式修饰符


含义 格式修饰符 说明
标志 - 输出数据左对齐,右边填补空格,系统默认为右对齐输出
标志 + 输出带符号数的正数时前面显示正号(+)
标志 空格 输出带符号数的正数时前面显示空格代替正号
标志 0 输出数据时指定左边不使用的空位自动填 0
标志 # 在八进制和十六进制数前分别显示前导符 0 和 0x
最小宽度 m(代表一个整数) 按宽度 m 输出,若数据长度小于 m,左边补空格,否则按实际输出
精度 .n(代表一个整数) 对于实数,指定小数点后的位数为 n 位(四舍五入);对于字符串,指定截取字符串前n个字符
长度 l 或 L 在 d、o、x、u 格式字符前,指定输出精度为 long 型;在 f、e、g 格式字符前,指定输出精度为 double 型
长度 h 或 H 在 d、o、x、u 格式字符前,指定输出精度为 short 型

32 位系统下:
int i = 0;

类型 字节大小 (Byte)
sizeof(int) 4
sizeof(i) 4
sizeof int 错误,2个关键字不能叠加
sizeof i 4

32 位系统下:
int *p = NULL;
int * 指针 32/8=4B

类型 字节大小 (Byte)
sizeof(p) 4,int * 大小
sizeof(*p) 4,int 大小

32 位系统下:
int a[100];

类型 字节大小 (Byte)
sizeof(a) 400
sizeof(a[100]) 4,不存在 a[100],但是只讨论类型大小
sizeof(&a) 4,取数组 a 的首地址
sizeof(&a[0]) 4,取数组 a[0] 的首地址
  • &a[0] 和 &a 值是一样的。
int b[100];
void fun(int b[100])
{
 sizeof(b);  //4B,取 b 的首地址
}

比较的时候,一般把常量放到左边。

if(NULL != p)
if(0 == x)

break 和 continue 的区别


while(1)
{
  if('#' == GetInputChar())
    break;或continue;
}
  • break:终止本层循环,只有一层循环,循环结束;
  • continue:终止本轮循环,进入下一轮循环。

多重循环中,应当将最长的循环放在最内层,最短的循环放在最外层,以减少 CPU 跨切循环层的次数。

void *:任意类型的指针都可以操作。

const 修饰的只读变量(readonly)


  • 修饰一般变量
int const i = 2;
const int i = 2;

等价于 const int i = 2;

  • 修饰指针
const int *p;
int const *p;
int * const p;
const int * const p;

等价于

  1. const int *p;
  2. int const *p;
  3. int * const p;
  4. const int * const p;

可以看出

  • 1 和 2 是相同的,const 修饰 p,p 是指针,p 是指针指向的对象,不可变;

  • 3 const 修饰 p,p 不可变,p 指向的对象;

  • 4 前一个 const 修饰 *p,后一个 const 修饰 p,指针 p 和 p 指向的对象都不可变。

  • extern 在另一个文件的声明

a.c 中定义 int i = 10;
b.c 中声明 extern int i; //正确
b.c 中声明 extern int i = 10; //错误,重定义

struct 和 class 的区别


struct:public 属性
class:private 属性

union 联合体


  • 大小足够容纳最宽的成员;
  • 大小能被其包含的所有基本数据类型的大小所整除;
  • 所有的数据成员具有相同的起始地址;
  • 适用于一些数据不可能在同一时间同时被用到。

大小端模式


int i = 1;

  • 大端模式(ARM 平台):字数据的高字节存储在低地址中,低字节则存储在高地址中。
    高存低,低存高。
0x0 0x0 0x0 0x1
低地址 高地址
  • 小端模式(x86 平台):字数据的高字节存储在高地址中,低字节则存储在低地址中。
    低存低,高存高。
0x0 0x0 0x0 0x1
高地址 低地址

经典案例:

在 x86 系统下:

#include "stdio.h"

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

运行结果:5, 2000000

&a+1 = 数组首地址 + sizeof(a) = &a + 5* sizeof(int),1 的大小取决于 a 的类型

ptr1

(int)a+1 = &(a[0][1 2 3] + a[1][0]),此时 1 根据 a 的类型取 1 字节。

x86 小端模式 ptr2

typedef 的使用


typedef struct student 
{
    //code 
}Stu_st, *Stu_pst;
  • struct student stu1 和 Stu_st stu1 没有区别;
  • struct student *stu2、Stu_pst stu2 和 Stu_st * stu2 没有区别。

第 2 章 符号

接续符 \


反斜杠的下一行之前不能有空格。

逻辑运算符


|| 和 && 的短路现象,只要前面的表达式能决定结果,后面的表达式不再执行。

++、-- 操作符


#include "stdio.h"

void main()
{
    int i = 3;
    int j = 0;
    
    j = (++i)+(++i)+(++i);
    printf("(++i)+(++i)+(++i) = %d", j);
}
  • 运行结果:(++i)+(++i)+(++i) = 16
  • 分析:++ 优先级高于 +,前2个先做运算,再与第3个作运算。
#include "stdio.h"

void main()
{
    int i = 3;
    int j = 0;
    
    j = (i++)+(i++)+(i++);
    printf("(i++)+(i++)+(i++) = %d", j);
}
  • 运行结果:(i++)+(i++)+(i++) = 12
  • 分析:++ 优先级高于 +,前2个先做运算,再与第3个作运算。

优先级表


优先级 运算符 名称 结合方向
1 [ ],( ),.,-> 数组下标,圆括号,成员选择(对象),成员选择(指针)
2 -,(类型),++--,*,&,!,~,sizeof 负号,强制类型转换,自增自减,取值,取地址,逻辑非,按位取反,长度运算
3 /,*,% 除,乘,余数(取模)
4 +-
5 <<,>> 左移,右移
6 >,>=,<,<= 大于,大于等于,小于,小于等于
7 ==,!= 等于,不等于
8 & 按位与
9 ^ 按位异或
10 | 按位或
11 && 逻辑与
12 || 逻辑或
13 ?: 条件运算符
14 =,/=,*=,%=,+=,-=,<<=,>>=,&=,^=,|= 赋值,除后,乘后,取余后,加后,减后,左移后,右移后,按位与后,按位异或后,按位或后
15 , 逗号运算符

容易出错的优先级问题


优先级 表达式 错误结果 实际结果
. 高于 * *p.f p所指对象的字段f(*p).f 对p取f偏移,作为指针*(p.f)
[ ] 高于 * int *ap[ ] ap是指向int数组的指针int (*ap)[ ] ap是元素为int指针的数组int *(ap[ ])
函数 ( ) 高于 * int *fp() fp是个函数指针,所指函数返回int int(*fp)() fp是个函数,返回int* int *(fp())
== 和 != 高于位运算 (val&mask != 0) (val&mask) != 0 val & (mask != 0)
== 和 != 高于赋值符 c=getchar()!= EOF (c=getchar()) != EOF c=(getchar() != EOF)
算术运算符高于位移 msb << 4+lsb (msb<<4) + lsb msb << (4+lsb)
逗号优先级最低 i=1, 2 i=(1, 2) (i=1), 2

第 3 章 预处理

内存对齐(1、2、4、8最大偶数对齐)


  • 为了访问未对齐的内存,处理器需要做两次内存访问,而对齐的内存访问仅需要一次访问。
    • 未对齐的情况
      一个字或者双字操作数跨越了4字节边界,或者一个四字操作数跨越了8字节边界,需要两次总线周期来访问内存。
    • 对齐的情况
      一个字起始地址是奇数但却没有跨越字边界被认为是对齐的,能够在一个总线周期中被访问。

字节对齐规则


  • 结构体等复杂类型按照它最长的成员的对齐方式进行对齐;
  • 对齐后的长度必须是成员中最大的对齐参数的整数倍;
  • 数组按照类型进行对齐,而不是按它的长度
  • 对齐的边界一定是1、2、4、8、16、32、64...中的一个。
#include "stdio.h"

void main()
{
    struct TestStruct1
    {
        char c1; //1
        short s; //2
        char c2; //1
        int i; //4
    };
    
    struct TestStruct1 a;   
    printf("%d\n", sizeof(a)); 
    printf("c1 %p, s %p, c2 %p, i %p\n",
           (unsigned int)(void *)&a.c1 - (unsigned int)(void *)&a,
           (unsigned int)(void *)&a.s - (unsigned int)(void *)&a,   
           (unsigned int)(void *)&a.c2 - (unsigned int)(void *)&a,     
           (unsigned int)(void *)&a.i - (unsigned int)(void *)&a);
}
  • 运行结果:
    12
    c1 0000000000000000, s 0000000000000002, c2 0000000000000004, i 0000000000000008

  • 分析:通过地址可以很容易看出内存分配情况

1 1 1 char、short
1 char
1 1 1 1 int,按最大的对齐
#include "stdio.h"

void main()
{
    struct TestStruct1
    {
        char c1; //1
        char c2; //1
        short s; //2
        int i; //4
    };
    
    struct TestStruct1 a;   
    printf("%d", sizeof(a)); 
}
  • 运行结果:8
  • 分析
1 1 1 1 char、char、short
1 1 1 1 int,按最大的对齐
#include "stdio.h"

void main()
{
    struct Test
    {
        int Num; //4 
        char *pcName; //4
        short sDate; //2
        char cha[2]; //1*2=2,最大长度是 1,不是 2
        short sBa[4]; //2*4=8,最大长度是 2,不是 8 
    }*p;
    
    printf("%d", sizeof(*p)); 
}
  • 运行结果:20
  • 分析:如果结构体中有数组,需要考虑数组个数,但是内存还是按类型来算的,不是类型*个数

第 4 章 指针和数组

&a[0] 和 &a 的区别


对于结果而言,&a[0] == &a

  • &a[0]:数组首元素的首地址;
  • &a:数组的首地址。

指针变量在 32 位系统下,永远占 4 字节。

内存与尺子


#include <stdio.h>

int main()
{
    int a[3][2]={(0,1),(2,3),(4,5)};
    int *p;
    p=a [0];
    printf("%d",p[0]);
}
  • 运行结果:1
  • 分析:,运算符先计算左边,在计算右边。右边操作数作为整个表达式的结果。
a[0][0] a[0][1] a[1][0] a[1][1] a[2][0] a[2][1]
int a [3][2]={ 1, 3, 5};

数组作为函数参数


  • 当一维数组作为函数参数的时候,编译器总是把它解析成一个指向其元素首地址的指针。
#include "stdio.h"

void fun(char a[ ]) //a[5] a[10] *a 都可以  
{
    int i = sizeof(a); //传递的是地址 
    char c = a[3];
    printf("%d %c\n", i, c);
}

void main()
{   
    char b[10] = "abcdefg";
    fun(b); //类型为 char * 
}

  • 运行结果:4 d
  • 分析:传递的是参数,不检验具体的标号。

第 5 章 内存管理

内存释放


定义指针变量的同时最好初始化为 NULL,用完指针之后也将指针变量的值设置为 NULL。
free 完之后,一定要给指针置 NULL。

栈、堆和静态区


内存分为三个部分:堆、栈和静态区。

  • 堆:由 malloc 系列函数或 new 操作符分配的内存。其生命周期由 free 或 delete 决定。在没有释放之前一直存在,直到程序结束。其特点是使用灵活,空间比较大,但容易出错;
  • 栈:保存局部变量。栈上的内容只在函数的范围内存在,当函数运行结束,这些内容也会自动被销毁。其特点是效率高,但空间大小有限;
  • 静态区:保存自动全局变量和 static 变量(包括 static 全局和局部变量)。静态区的内容在总个程序的生命周期内都存在,由编译器在编译的时候分配。

asset 宏可以帮助我们定位错误,而不是排除错误。

malloc 分配内存


char *p1 = “abcdefg”;
char *p2 = (char *)malloc(sizeof(char)*strlen(p1)+1*sizeof(char));
strcpy(p2,p1);
  • 分析:需要考虑字符串常量的结束标志“\0”,不要因为 char 类型大小为 1 个 byte 就省略 sizof(char)这种写法。这样只会使你的代码可移植性下降。

memset 初始化


int a[10] = {0}; //初始化为 0 

或者使用memset

int a[10];
memset(a, 0, sizeof(a));
  • 分析:memset 第 1 个参数是内存起始地址,第 2 个参数是要设置的值,第 3 个参数是单位为字节的内存大小。

第 6 章 函数

getchar()


函数原型为

int getchar(void);

可以看出返回值为 int

函数规则


  • 复杂的函数中,在分支语句、循环语句结束之后需要适当的注释,方便区分各分支或循环体。
//end "for(condition)"
//end "if(condition)"
//end "while(condition)"
  • 代码行最大长度宜控制在80个字符以内,较长的语句、表达式等要分成多行书写。
  • 函数体的规模要小,尽量控制在80行代码以内。
  • 函数的 static 局部变量是函数的“记忆”存储器,建议尽量少用 static 局部变量。
  • 参数个数尽量控制在 4 个或 4 个以内。

函数递归


一个简单但易出错的递归例子

#include "stdio.h"

void fun(int i)
{
    if(i>0)
    {
        fun(i/2);
    }
    printf("%d\n", i);
}

int main()
{
    fun(10);
    return 0;
}
  • 运行结果:
    0
    1
    2
    5
    10
  • 分析:最内层的最先输出

不使用任何变量编写 strlen 函数

int my_strlen(const char *strDest)
{
    assert(NULL != strDest); //参数入口校验
    return ('\0' != *strDest) ? (1 + my_strlen(strDest+1)) : 0;
}
  • 分析:尽量不要使用递归,也要注意递归的层次不要太深,防止出现栈溢出的错误,同时要注意递归的停止条件一定要正确。

第 7 章 文件结构

需要对外公开的常量放在头文件中,不需要对外公开的常量放在定义文件的头部。

第 8 章 关于面试的秘密

  • 学生就是学生,穿着符合自己身份就行了
  • 谈吐要符合自己身份,切忌不懂装懂、满嘴胡咧咧
  • 态度是一种习惯,习惯决定一切,忌浮躁、忌傲慢、忌眼高手低
  • 要学会尊敬别人和懂礼貌

让考官眼前一亮的简历


  • 自我介绍一定要简明而不简单、突出重点、突出你的长处、突出你和别人不一样的地方,争取一个好的第一印象。
  • 个人信息不用写身高、体重,不要写错学校相关信息,不要泄露自己的家庭电话和身份证号等个人信息
  • 不要硬件、软件都写,让人觉得你都不行,不要写精通,要写掌握、熟练掌握,写上你看过的教材之外的一些经典专业书籍,面试一般不会超出你简历上所写的内容,而且会挑你最熟悉的知识问。
  • 成绩表是应届生必须要准备的
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,123评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,031评论 2 384
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,723评论 0 345
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,357评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,412评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,760评论 1 289
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,904评论 3 405
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,672评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,118评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,456评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,599评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,264评论 4 328
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,857评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,731评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,956评论 1 264
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,286评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,465评论 2 348