基本数据类型分析
数据类型
什么是数据类型 ?
? 数据类型可以理解为 固定内存大小的别名
? 数据类型是创建变量的模子
类型的本质
char
short
int
1 byte
2 byte
4 byte
内存空间
char c
short s
int i
变量本质
? 变量是一段实际 连续存储空间的别名
? 程序中 通过变量来申请并命名存储空间
? 通过 变量的名字可以 使用存储空间
auto,register,static 分析
auto? C 语言中的变量可以有自己的属性
? 在定义变量的时候可以加上“ 属性” 关键字
? “ 属性” 关键字指明变量的特有意义
?auto即C语言中局部变量的默认属性
?编译器 默认所有的 局部变量都是 auto的
? static 关键字指明变量的“ 静态” 属性
? static 关键同时具有“ 作用域限定符” 的意义
?static 修饰的 局部变量存储在程序 静态区
?static 的另一个意义是 文件作用域标示符
―static 修饰的 全局变量作用域只是声明的文件中
―static 修饰的 函数作用域只是声明的文件中
register
? register 关键字指明 将变量存储于寄存器中
? register 只是请求寄存器变量 , 但不一定请求成功
?register 变量的必须是CPU 寄存器可以接受的值
? 不能用& 运算符获取register 变量的地址
? 小结
? auto 变量存储在程序的 栈中 , 默认属性
? static 变量存储在程序 静态区中
? register 变量请求存储于 CPU 寄存器中
if,switch,do,while,for 分析
析 分支语句分析 -- if
?if 语句用于根据条件选择执行语句
?else 不能独立存在且总是与它最近的if 相匹配
?else 语句后可以接连其他 if 语句
?if 语句中零值比较的注意点
? bool 型变量 应该直接出现于条件中 , 不要进行比较
? 普通变量和 0 值比较时 , 0 值应该出现在比较符号左边
? float 型变量不能直接进行 0 值比较 , 需 要定义精度
析 分支语句分析 -- switch
? switch 语句对应单个条件多个分值的情形
? 每个case 语句分支必须要有 break , 否则会导致分支重叠
? default 语句有必要加上 , 以处理特殊情况
表达式
default 语句1 1 语句2 2 语句3 3
break break break break
?case 语句中的值只能是 整型或 字符型
?case 语句排列顺序分析
? 按字母或数字顺序排列各条语句
? 正常情况放在前面 , 异常情况放在后面
? default 语句只用于处理真正的默认情况
分支语句分析
? 小结
? if语句实用于需要 “按片”进行判断的情形中
? switch语句实用于需要对各个 离散值进行分别判
断的情形中
? if语句可以安全从功能上代替switch语句,但
switch语句无法代替if语句
? switch语句对于多分支判断的情形更加简洁
循环语句分析
? 循环语句的基本工作方式
? 通过条件表达式判定是否执行循环体
? 条件表达式遵循if 语句表达式的原则
?do,while,for 的区别
? do 语句先执行后判断 , 循环体至少执行一次
? while 语句先判断后执行 , 循环体可能不执行
? for 语句先判断后执行 , 相比while 更简洁
?break 和continue 的区别
? break 表示终止循环的执行
? continue 表示终止本次循环体 , 进入下次循环执行
注意:swicth语句不能与continue同时用
goto,void,extern,sizeof 分析
遭人遗弃的 goto
? 高手潜规则 : 禁用goto
? 项目经验 : 程序质量与goto 的出现次数成反比
? 最后的判决 : 将goto 打入冷宫
void 的意义
? void 修饰函数返回值和参数
? 如果函数没有返回值 , 那么应该将其声明为void 型
? 如果函数没有参数 , 应该声明其参数为void
void 修饰函数返回值和参数仅为了表示 无
不存在void 变量
C 语言没有定义void 究竟是多大内存的别名
没有void 的标尺
无法在内存中裁剪出void 对应的变量
? void 指针的意义
? C 语言规定只有相同类型的指针才可以相互赋值
? void* 指针作为左值用于“ 接收” 任意类型的指针
? void* 指针作为右值赋值给其它指针时需要强制类型转换
extern 中隐藏的意义
? extern 用于声明外部定义的变量和函数
? extern 用于“ 告诉” 编译器用C 方式编译
C++ 编译器和一些变种C 编译器默认会按“ 自己” 的方式编译
函数和变量 , 通过extern 关键可以命令编译器“ 以标准C 方
式进行编译” 。
为sizeof 正名
? sizeof 是编译器的内置指示符 , 不是函数
? sizeof 用于“ 计算” 相应实体所占的内存大小
? sizeof 的值在编译期就已经确定
const 和volatile 分析
const 修饰变量
? 在C 语言中const 修饰的变量是 只读的 , 其 本质还是变量
? const 修饰的变量会在内存占用空间
? 本质上const 只对编译器有用 , 在运行时无用
原来const 不是真的常量
const 修饰数组
? 在C 语言中const 修饰的数组是只读的
? const 修饰的数组空间不可被改变
const 修饰指针(判断const修饰的是p还是*p )
? const int* p; //p 可变 ,p 指向的内容不可变
? int const* p; //p 可变 ,p 指向的内容不可变
? int* const p; //p 不可变 ,p 指向的内容可变
? const int* const p; //p 和p 指向的内容都不可变
口诀 : 左数右指
当const 出现在 * 号左边时指针指向的数据为常量
当const 出现在* 后右边时指针本身为常量
const 修饰函数参数和返回值
? const 修饰函数参数表示 在函数体内 不希望改变参数的值
? const 修饰函数返回值表示返回值不可改变 , 多用于返回指针的情况
深藏不漏的volatile
? volatile 可理解为“ 编译器警告指示字”
? volatile 用于告诉编译器必须每次去内存中取变量值
? volatile 主要修饰可能被多个线程访问的变量
? volatile 也可以修饰可能被未知因数更改的变量
struct 和union 分析
由结构体产生柔性数组
? 柔性数组即数组大小待定的数组
? C 语言中结构体的最后一个元素可以是大小未知的数组
? C 语言中可以由结构体产生柔性数组
union 和struct 的区别
? struct 中的每个域在内存中都独立分配空间
? union 只分配最大域的空间 , 所有域共享这个空间
union 使用的注意事项
? union 的使用受系统大小端的影响
enum 和typedef 分析
枚举类型的使用方法
? enum 是一种自定义类型
? enum 默认常量在前一个值的基础上依次加1
? enum 类型的变量只能取定义时的离散值
枚举类型和#define 的区别
?#define 宏常量只是简单的进行值替换 , 枚举常
量是真正意义上的常量
?#define 宏常量无法被调试 , 枚举常量可以
?#define 宏常量无类型信息 , 枚举常量是一种特
定类型的常量
面试中……
考官 : 你能说说typedef 具体的意义吗 ?
应聘者 :typedef 用于定义一种新的类型 。。。
? typedef 用于给一个已经存在的数据类型重命名
? typedef 并没有产生新的类型
? typedef 重定义的类型不能进行unsigned 和signed 扩展
typedef 和#define 的区别
? typedef 是给已有类型取别名
? #define 为简单的字符串替换 , 无别名的概念
注释符号
注释符号
? 注释规则小结
? 编译器会在编译过程删除注释 , 但不是简单的删除而是用空
格代替
? 编译器认为双引号括起来内容都是字符串 , 双斜杠也不例外
? “/*……*/” 型注释不能被嵌套
注释符号
? 你觉得Y=x/*p 是什么意思? ?
作者本意 : 把 x 除以*p 的结果赋值给 y
编译器 : 将/* 作为一段注释的开始 , 把 /* 后的内
容都当成注释内容 , 直到*/ 出现为止
在编译器看来 , 注释和其它程序元素都是平等的 , 所以 ,
作为程序员也不能看轻注释 。
? 出色的注释你来写
? 注释应该准确易懂 , 防止二义性 , 错误的注释有害而无利
? 注释是对代码的提示 , 避免臃肿和喧宾夺主
? 一目了然的代码避免加注释
? 不要用缩写来注释代码 , 这样可能会产生误解
? 注释用于阐述原因而不是用于描述程序的运行过程
接续符和转义符
? C 语言中的接续符(\) 是指示编译器行为的利器
? 接续符的使用 :
? 编译器会将反斜杠剔除 , 跟在反斜杠后面的字符自动解到前
一行
? 在接续单词时 , 反斜杠之后不能有空格 , 反斜杠的下一行之
前也不能有空格
? 接续符适合在定义宏代码块时使用
? C 语言中的转义符(\) 主要用于表示无回显字符 , 也可
用于表示常规字符
小结
? C 语言中的反斜杠(\) 同时具有接续符和转义符的作用
? 当反斜杠作为接续符使用时可直接出现在程序中
? 当反斜杠作为转义符使用时需出现在字符或字符串中
单引号和双引号
?C 语言中的 单引号 用来表示 字符常量
?C 语言中的 双引号 用来表示 字符串常量
‘a’ 表示字符常量
在内存中占1 个字节
’a’+1 表示’a’ 的ASCII 码加1 , 结果为‘b’
“a” 表示字符串常量
在内存中占2 个字节
“a”+1 表示指针运算 , 结果指向“a” 结束符’\0’
单引号和双引号
?小结
? 本质上单引号括起来的一个字符代表 整数
? 双引号括起来的字符代表一个 指针
? C 编译器接受字符和字符串的比较 , 可意义是错误的
? C 编译器允许字符串对字符变量赋值 , 其意义是可笑的
逻辑运算符使用分析
? 逻辑运算符&&,|| 和! 真的简单吗 ?
短路规则 :
?|| 从左向右开始计算 , 当遇到为真的条件
时停止计算 , 整个表达式为真 ; 所有条件
为假时表达式才为假 。
?&& 从左向右开始计算 , 当遇到为假的条
件时停止计算 , 整个表达式为假 ; 所有条
件为真时表达式才为真。
? “!” 到底是神马?
C 语言中的逻辑符“!” 只认得0 , 只知道见了0 就返回1 。
因此当其作用的值不是0 时 , 其结果为0 。
? 三目运算符(a?b:c) 可以作为逻辑运算符的载体
? 规则 : 当a 的值为真时 , 返回b 的值 ; 否则返回c 的值
位运算符分析
? 在C 语言中的位运算符右移 >>
左移 <<
取反 ~
按位异或 ^
按位或 |
按位与 &
? 左移和右移注意点
? 左移运算符<< 将运算数的二进制位左移
• 规则 : 高位丢弃 , 低位补0
? 右移运算符>> 把运算数的二进制位右移
• 规则 : 高位补符号位 , 低位丢弃
注意补齐位的值 !
? 防错准则 :
? 避免位运算符 , 逻辑运算符和数学运算符同时出现在一个表达式
中
? 当位运算符 , 逻辑运算符和数学运算符需要同时参与运算时 , 尽
量使用括号() 来表达计算次序
小技巧 :
? 左移n 位相当于乘以2 的n 次方 , 但效率比数学运算符高
? 右移n 位相当于除以2 的n 次方 , 但效率比数学运算符高
++,-- 操作符使用分析
++, -- 操作符使用分析
? 一对令人头疼的兄弟
你觉得这个表达式的值是多少 ?
? 笔试面试中的++i+++i+++i
a+++b
a++ + b
a + ++b
? 法 贪心法 - -- ++, -- 表达式的阅读技巧
? 编译器处理的每个符号应该 尽可能多的包含字符
? 编译器以 从左向右的顺序一个一个尽可能多的读入字符
? 当即将读入的字符不可能和已读入的字符组成合法符号为止
优先级和类型转换分析
? C 语言隐式类型转换
? 算术运算式中 , 低类型转换为高类型
? 赋值表达式中 , 表达式的值转换为左边变量的类型
? 函数调用时 , 实参转换为形参的类型
? 函数返回值 ,return 表达式转换为返回值类型
int -> unsigned int -> long -> unsigned long -> double
float
char
short
宏定义与使用分析
? #define 定义宏常量可以出现在代码的任何地方? #define 从本行开始 , 之后的代码都可以使用这个宏常量
? #define 表达式给有函数调用的假象 , 却 不是函数
? #define 表达式可以比函数更强大
? #define 表达式比函数更容易出错
? 宏表达式在 预编译期被处理 , 编译器不知道宏表达式
的存在
? 宏表达式用“ 实参” 完全替代形参 , 不进行任何运算
? 宏表达式 没有任何的“ 调用” 开销
? 宏表达式中 不能出现递归定义
条件编译使用分析
? 条件编译的行为类似于C 语言中的if…else? 条件编译是预编译指示命令 , 用于控制是否编译某段代码
? #include 的本质是 将已经存在的文件内容嵌入到当前文件中
? #include 的间接包含同样会产生嵌入文件内容的动作
条件编译的意义
? 条件编译使得我们可以按不同的条件编译不同的代码段 ,
因而可以产生不同的目标代码
? #if…#else…#endif 被预编译器处理 ; 而if…else 语句被
编译器处理 , 必然被编译进目标代码
? 实际工程中条件编译主要用于一下两种情况 :
? 不同的产品线共用一份代码
? 区分编译产品的调试版和发布版
小结
? 通过编译器命令行能够定义预处理器使用的宏
? 条件编译可以避免重复包含头同一个头文件
? 条件编译是在工程开发中可以区别不同产品线的代码
? 条件编译可以定义产品的发布版和调试版
break的用法
问题
break 关键字可以用于按条件退出循环。当break的条件满足时,会直接退出循环,而不再判断循环条件和进行循环变量的递增或者递减。
步骤
实现此案例需要按照如下步骤进行。
步骤一:break的用法
代码如下所示:
- #include <stdio.h>
- int main()
- {
- for (int i = 0; ; i++)
- {
- int num;
- printf("请输入一个整数(输入0退出):");
- scanf("%d", &num);
- if (num > 0)
- printf("%d是一个正数。\n", num);
- else if (num < 0)
- printf("%d是一个负数。\n", num);
- else
- break;
- }
- return 0;
- }
上述代码中,以下代码:
- for (int i = 0; ; i++)
设置一个死循环。
上述代码中,以下代码:
- int num;
- printf("请输入一个整数(输入0退出):");
- scanf("%d", &num);
首先,定义一个整型变量num,用于存储输入的一个整数。
然后,使用函数printf提示输入的一个整数,并提示输入0退出。
最后,使用函数scanf输入的一个整数到变量score中。
上述代码中,以下代码:
- if (num > 0)
- printf("%d是一个正数。\n", num);
如果输入的整数大于0,则打印是一个正数。
上述代码中,以下代码:
- else if (num < 0)
- printf("%d是一个负数。\n", num);
如果输入的整数小于0,则打印是一个负数。
上述代码中,以下代码:
- else
- break;
如果输入的整数等于0,则退出循环。如此使用break语句,可以实现不定次数循环。
完整代码
本案例的完整代码如下所示:
- #include <stdio.h>
- int main()
- {
- for (int i = 0; ; i++)
- {
- int num;
- printf("请输入一个整数(输入0退出):");
- scanf("%d", &num);
- if (num > 0)
- printf("%d是一个正数。\n", num);
- else if (num < 0)
- printf("%d是一个负数。\n", num);
- else
- break;
- }
- return 0;
- }
循环次数不确定的循环
问题
实现“猜数字”小游戏。
步骤
实现此案例需要按照如下步骤进行。
步骤一:循环次数不确定的循环
- #include <stdio.h>
- #include <time.h>
- #include <stdlib.h>
- int main()
- {
- srand(time(0));
- int ran = rand() % 100 + 1;
- for(;;)
- {
- int num;
- printf("请输入一个1到100之间的整数:");
- scanf("%d", &num);
- if (num < 0 || num > 100)
- printf("输入的数不在1到100之间。\n");
- else if (num == ran)
- break;
- else if (num > ran)
- printf("大了\n");
- else
- printf("小了\n");
- }
- printf("猜对了\n");
- return 0;
- }
上述代码中,以下代码:
- srand(time(0));
设置随机的算法种子。要使用随机数,需要此句。
上述代码中,以下代码:
- int ran = rand() % 100 + 1;
使用函数rand生成一个1~100之间的随机数。
上述代码中,以下代码:
- for(;;)
设置一个死循环。
上述代码中,以下代码:
- int num;
- printf("请输入一个1到100之间的整数:");
- scanf("%d", &num);
首先,定义一个整型变量num,用于存储输入一个1~100之间的整数。
然后,使用函数printf提示输入整数。
最后,使用函数scanf输入整数到变量score中。
上述代码中,以下代码:
- if (num < 0 || num > 100)
- printf("输入的数不在1到100之间。\n");
容错保护,输入的整数不在1~100之间,则重新输入。
上述代码中,以下代码:
- else if (num == ran)
- break;
如果输入的整数正好与随机数相等,则使用break语句退出。
上述代码中,以下代码:
- else if (num > ran)
- printf("大了\n");
如果输入的整数大于随机数,则提示大了。
上述代码中,以下代码:
- else
- printf("小了\n");
如果输入的整数小于随机数,则提示小了。
上述代码中,以下代码:
- printf("猜对了\n");
使用break语句退出循环后,提示猜对了。
完整代码
本案例的完整代码如下所示:
- #include <stdio.h>
- #include <time.h>
- #include <stdlib.h>
- int main()
- {
- srand(time(0));
- int ran = rand() % 100 + 1;
- for(;;)
- {
- int num;
- printf("请输入一个1到100之间的整数:");
- scanf("%d", &num);
- if (num < 0 || num > 100)
- printf("输入的数不在1到100之间。\n");
- else if (num == ran)
- break;
- else if (num > ran)
- printf("大了\n");
- else
- printf("小了\n");
- }
- printf("猜对了\n");
- return 0;
- }
continue用法
问题
continue 关键字可以用于按条件中止本次循环,继续下次循环。break 退出循环,continue不会退出循环,只是不再继续执行本次循环。
步骤
实现此案例需要按照如下步骤进行。
步骤一:continue用法
代码如下所示:
- #include <stdio.h>
- int main()
- {
- for (int i = 0; i <= 100; i++)
- if(i % 2)
- continue;
- else
- printf("%d ", i);
- printf("\n");
- return 0;
- }
上述代码中,以下代码:
- for (int i = 0; i <= 100; i++)
设置一个循环,循环100次。
上述代码中,以下代码:
- if(i % 2)
- continue;
- else
- printf("%d ", i);
判断循环变量,如果循环变量是奇数,则使用continue语句继续循环。否则打印这个偶数。
完整代码
本案例的完整代码如下所示:
- #include <stdio.h>
- int main()
- {
- for (int i = 0; i <= 100; i++)
- if(i % 2)
- continue;
- else
- printf("%d ", i);
- printf("\n");
- return 0;
- }
二重循环的使用
问题
打印如下图-2所示的图形:
图-2
步骤
实现此案例需要按照如下步骤进行。
步骤一:二重循环的使用
代码如下所示:
- #include <stdio.h>
- int main()
- {
- for (int i = 0; i < 5; i++)
- {
- for (int j = 0; j <= i; j++)
- printf("*");
- printf("\n");
- }
- return 0;
- }
上述代码中,以下代码:
- for (int i = 0; i < 5; i++)
设置一个外层循环,循环5次,代表打印5行。
上述代码中,以下代码:
- for (int j = 0; j <= i; j++)
- printf("*");
设置一个内层循环,循环的次数由外层循环变量i决定,当i等于0时,打印一个*,当i等于1时,打印两个*,依次类推。
由此可以看出外层循环负责打印多少行,内层循环负责在一行中打印多少个*。
上述代码中,以下代码:
- printf("\n");
此句非常重要,因为内层循环退出时,表示某行的*已经打印完毕,此时需要打印一个回车,以换行。
完整代码
本案例的完整代码如下所示:
- #include <stdio.h>
- int main()
- {
- for (int i = 0; i < 5; i++)
- {
- for (int j = 0; j <= i; j++)
- printf("*");
- printf("\n");
- }
- return 0;
- }
二重循环的使用(续1)
问题
打印如下图-3所示的图形:
图-3
步骤
实现此案例需要按照如下步骤进行。
步骤一:switch语句的使用
代码如下所示:
- #include <stdio.h>
- int main()
- {
- for (int i = 0; i < 5; i++)
- {
- for (int j = 0; j <= i * 2; j++)
- printf("*");
- printf("\n");
- }
- return 0;
- }
上述代码中,以下代码:
- for (int i = 0; i < 5; i++)
设置一个外层循环,循环5次,代表打印5行。
上述代码中,以下代码:
- for (int j = 0; j <= i * 2; j++)
- printf("*");
设置一个内层循环,循环的次数由外层循环变量i * 2决定。当i等于0时,条件为j <= 0,所以循环一次,打印一个*。当i等于1时,条件为j <= 1 * 2,所以循环三次,打印三个*,依次类推。
由此可以看出外层循环负责打印多少行,内层循环负责在一行中打印多少个*。
上述代码中,以下代码:
- printf("\n");
此句非常重要,因为内层循环退出时,表示某行的*已经打印完毕,此时需要打印一个回车,以换行。
完整代码
本案例的完整代码如下所示:
- #include <stdio.h>
- int main()
- {
- for (int i = 0; i < 5; i++)
- {
- for (int j = 0; j <= i * 2; j++)
- printf("*");
- printf("\n");
- }
- return 0;
- }