外观
02.C语言查缺补漏
约 3727 字大约 12 分钟
C个人随笔记录
2020-10-25
.
十、sizeof 关键字
功能∶用于获取类型或者变量所占用的内存大小(字节)
用法︰
sizeof ( type )
sizeof ( variable )
sizeof variable
十一、C语言中的奇葩整形(long)
long在使用不同编译器时,可能占用的内存不同
long通常占用4字节内存,也可能占用8字节内存
long long表示整型,固定占用8字节内存
long long是 long long int 的缩写形式
十二、连续变量定义和连续赋值
type v1,v2,v3;
v1 = v2 = v3;
十三、三目运算符(条件运算符)
本质:if ...else...语句的缩写版
语法:(条件)?(为真时的语句):(为假时的语句)
十四、逗号运算符
用逗号分开的表达式的值分别结算,但整个表达式的值是最后一个表达式的值。
C = (a = 2,b = 3, a + b);
Func(((1,2),(3,4,5)));只调用了一个参数5,逗号表达式。
十五、自增(++)与自减(--)运算符
前置:先自增(自减),再取值
- ++v;<>(v = v+1,v); --v;<>(v = v-1,v);
后置:先取值,再自增(自减)
- v++;<>(v = v+1,v-1); v--;<>(v = v-1,v+1);
十六、C语言中的常量类型
字面量
·直接表示值含义的符号,如:5,'a',"Delphi"
宏常量(符号化的字面量)
·通过#define定义,间接表示值的符号,如:FIV 🡪 5.5
枚举常量
·通过enum定义,间接表示值的符易,如:First 🡪 1
字面量有其默认类型,也可通过后缀指定其类型
#define定义的宏常量可以是任意类型
enum定义的枚举常量只能是整型
十七、数组
数组的内存分布
数组在计算机底层是一片连续的内存,用于存储数组元素
数组的大小字节数可以用sizeof 获取(单位∶字节)
计算数组大小
type Name [ ] = { vo,V1,... , Vn-1}; //共n个元素
sizeof ( Name ) / sizeof ( Name[0] ); //计算结果为n
int a[10]是int [10]类型。
- 二维数组的初始化

十八、字符串常量的本质是字符数组
#include <stdio.h>
int main()
{
int i = 0;
int e = 0;
for (i = 0; i < strlen("exposed Joker") + 1; i++)
{
e = "exposed Joker"[i];
printf("%c", e);
}
return 0;
}十九、转义字符
设:char s[10] = "\n\\\r";则 : s的长度为 3(\n、\\、\r)
| 转义字符 | 意义 | ASCII码值(十进制) |
|---|---|---|
| \a | 响铃(BEL) | 7 |
| \b | 退格(BS) ,将当前位置移到前一列 | 8 |
| \f | 换页(FF),将当前位置移到下页开头 | 12 |
| \n | 换行(LF) ,将当前位置移到下一行开头 | 10 |
| \r | 回车(CR) ,将当前位置移到本行开头 | 13 |
| \t | 水平制表(HT) (跳到下一个TAB位置) | 9 |
| \v | 垂直制表(VT) | 11 |
| \ | 代表一个反斜线字符''' | 92 |
| ' | 代表一个单引号(撇号)字符 | 39 |
| " | 代表一个双引号字符 | 34 |
| ? | 代表一个问号 | 63 |
| \0 | 空字符(NUL) | 0 |
| \ddd | 1到3位八进制数所代表的任意字符 | 三位八进制 |
| \xhh | 十六进制所代表的任意字符 | 十六进制 |
二十、同名变量
1、Void是基础类型,但不是基础数据类型
2、同名变量问题
不同函数中的局部变量可以同名(不会产生冲突)
全局变量不能同名(会产生命名冲突)
当局部变量和全局变量同名时,优先使用局部变量
3、同名变量规则
存在多个同名变量时,优先使用最近定义的变量。
二十一、变量的作用域
变量的作用域指的是变量定义后的可访问范围
不同变量的作用域可以有重叠
不同名变量在重叠作用域内可分别访问
在重叠作用域内,只可访问最近定义的同名变量

全局变量的作用域
全局作用域:可在程序的各个角落访问并使用
文件作用域:只能在当前代码文件中访问并使用
全局变量的作用域可能被局部变量覆盖(同名局部变量)
工程开发中,全局变量通常以g_作为前缀命名(工程约定)
进入死循环
#include <stdio.h>
int main(void)
{
int i = 2;
int e = 0;
while (printf("===%d===\n\r", i), i < 5)
{
int i = 10;
i++;
printf("%d\n\r", i);
}
return 0;
}二十二、生命期
全局数据区:存放全局变量,静态变量
栈空间:存放函数参数,局部变量
堆空间:用于动态创建变量
生命期:变量从创建到销毁的时间(即:合法可用的时间)
不同变量的生命周期
全局数据区中的变量
程序开始运行时创建,程序结束时被销毁,整个程序运行期合法可用
栈空间中的变量
进入作用域时创建,离开作用域时销毁(自动销毁)
- 局部变量在函数调用返回后销毁
二十三、生命期与作用域
1、作用域与生命期无本质联系
作用域规则是语法层面对变量是否可访问的规定
生命期是二进制层面上变量存在于内存中的时间
- 可能的情况
√作用域外无法访问的变量,可能在其生命期中(静态局部变量)
√作用域内可访问的变量,可能已经被销毁(堆变量)
√生命期中的变量,可能无法访问(文件作用域全局变量)
二十四、静态变量
static是C语言中的关键字
static修饰的局部变量创建于全局数据区(拥有程序生命期)
static修饰的全局变量只有文件作用域(文件之外无法访问)
static局部变量只会初始化一次,作用域与普通变量无异
二十五、全局数据区里默认为0,可以不初始化
int func(int x)
{
static int s_var;// 全局数据区中的变量,默认初始化为0
s_var += x;
return s_var;
}
二十六、变量的生命期由变量存储位置决定
static将变量存储于全局数据区,默认初始化为0
auto将变量存储于栈空间,默认初始化为随机值
register将变量存储于寄存器,默认初始化为随机值
变量生命期指变量合法可用的时间
生命期是变量存在于内存中的时间
作用域与生命期无本质联系
作用域和生命期用于判断变量是否可访问

二十七、不写返回语句,默认返回类型为int,下图程序正确,但是不推荐。
func(int var)
{
var + t;
return var;
}
int main()
{
int r = func(1);
printf("r = %d\n", r);
return 0;
}
二十八、再看逗号表达式
func(((1,2),(3,4,5)));
只调用了一个参数5,逗号表达式。

二十九、递归思想
递归是一种数学上分而自治的思想
递归程序中的表现为函数自调用
递归解法必须要有边界条件,否则无解
编写递归函数时不要陷入函数的执行细节
函数是一种代码复用的手段
把实现某个功能的代码片段进行封装(当作一个整体)
给这个代码片段一个合适的名字(通过名字使用代码)
定义参数(定义代码片段需要处理的问题)
三十、宏与函数
1、宏与函数不同
宏不是函数,使用宏没有函数调用的过程
函数调用先传递参数值,然后跳转执行函数体,最后返回
使用宏只是单纯"代码复制粘贴",然后替换参数
同一个函数,无论调用多少次,都执行相同的函数体代码
同一个宏,每次使用都会"复制粘贴"相同代码
宏常量在本质上与字面量相同(真正意义的常量)
2、编译器组成简介
A. 预处理模块∶处理所有宏以及#开头的语句(复制粘贴替换)
B. 编译模块︰将C程序翻译成二进制程序
C. 链接模块︰将二进制程序组合成可执行程序
三十一、指针
1、指针与内存地址
因为是变量,所以用于保存具体值
特殊之处,指针保存的值是内存中的地址
内存地址是什么?
内存是计算机中的存储部件,每个存储单元有固定唯一的编号
内存中存储单元的编号即内存地址
2、获取地址
C语言中通过&操作符获取程序元素的地址
&可获取变量,数组,函数的起始地址
内存地址的本质是一个无符号整数(4字节或8字节)
3、指针定义语法:type* pointer
type-数据类型,决定访问内存时的长度范围
*-标志,意味着定义一个指针变量
pointer - 变量名,遵循C语言命名规则
int main(void)
{
char* pChar;
short* pShort;
int* pInt;
float* pFloat;
double* pDouble;
return 0;
}4、指针内存访问:*pointer
指针访问操作符(*)作用于指针变量即可访问内存数据
指针的类型决定通过地址访问内存时的长度范围
指针的类型统一占用4字节或8字节
- 即:sizeof(type*) ==4或sizeof(type*) == 8
5、灵魂三问
[指针类型和普通类型之间的关系是什么?]
[Int* 和int有什么关系?]
[如何看待"内存地址+长度 才能访问内存中的数据"?]
[不同类型的指针可以相互赋值吗?]
Type* 类型的指针只保存Type类型变量的地址
禁止不同类型的指针相互赋值
禁止将普通数值当作地址赋值给指针
注意∶指针保存的地址必须是有效地址
6、通过指针参数
能够实现函数交换变量的值
能够从函数中"返回"多个值
7、int a[] = {1,2,3,4,5}; &a和a有什么区别?
&a与a在数值上相同,但是意义上不同
&a代表数组地址,类型为:int(*)[5]
a代表数组0号元素地址,类型为:int*
指向数组的指针:int (*pName) [5] = &a;
#include <stdio.h>
int main()
{
int a[] = { 1, 2, 3, 4, 0 };
int* p = a; // a 的类型为 int*, &a[0] ==> int*
int(*pa)[5] = &a;
printf("%p, %p\n", p, a);
p++;
*p = 100; // a[1] = 100;
printf("%d, %d\n", *p, a[1]);
printf("%p, %p\n", &a, a);
p = pa; // WARNING !!!!
p = a;
while (*p)
{
printf("%d\n", *p);
p++;
}
return 0;
}
8、指针与数组的等价用法

9、字符串拾遗
问:C语言中的字符串常量是什么类型?
答:char* 类型,一种指针类型
printf("%s\n", "Exposed Joker");
10、指针移动组合拳:int v = *p++;
解读:
指针访问操作符(*)和自增运算操作符(++)优先级相同
所以,先从p指向的内存中取值,然后p进行移动
等价于:
int v = *p;
p++;
#include <stdio.h>
int main()
{
int a[] = { 1, 2, 3 };
int* p = a;
int v = *p++;
char* s = NULL;
printf("%p\n", "D.T.Software");
printf("%p\n", "D.T.Software");
printf("v = %d, *p = %d\n", v, *p);
printf("First = %c\n", *"D.T.Software");
s = "D.T.Software";
while (*s) printf("%c", *s++);
printf("\n");
return 0;
}
11、函数与内存
(1)深入函数之旅
函数的本质是一段内存中的代码(占用一片连续内存)
函数拥有类型,函数类型由返回类型和参数类型列表组成

(2)又一些事实
函数名就是函数体代码的起始地址(函数入口地址)
通过函数名调用函数,本质为指定具体地址的跳转执行
因此,可定义指针,保存函数入口地址
(3)函数指针(Type func(Type1 a, Type2 b))
函数名即函数入口地址,类型为Type(*)(Type1,Type2)
对于func的函数,&func与func数值相同,意义相同
指向函数的指针:Type (*pFunc)(Type1,Type2) = func;
(4)函数指针参数
函数指针的本质还是指针(变量,保存内存地址)
可定义函数指针参数,使用相同代码实现不同功能
注意!
函数指针只是单纯的保存函数的入口地址
因此,① 只能通过函数指针调用目标函数 ;② 不能进行指针移动(指针运算)
12、再论数组参数
函数的数组形参退化为指针,因此,不包含数组实参的长度信息。使用数组名调用时,传递的是0号元素的地址。

函数名的本质是函数体的入口地址
函数类型由返回类型和参数类型列表组成
可定义指向函数的指针: Type (*pFunc) (Type1,Type2);
函数指针只是单纯的保存函数的入口地址(不能进行指针运算)
三十二、堆空间
1、堆空间的本质
备用的"内存仓库",以字节为单位预留的可用内存
程序可在需要时从"仓库"中申请使用内存(动态借)
当不需要再使用申请的内存时,需要及时归还(动态还)
2、预备知识 - void*
void类型是基础类型,对应的指针类型为void*
void是指针类型,其指针变量能够保存地址
通过void指针无法获取内存中的数据(无长度信息)
3、堆空间的使用
工具箱:stdlib.h
申请:void* malloc ( unsigned bytes )
归还:void free( void* p )

4、堆空间的使用原则
有借有还,再借不难(杜绝只申请,不归还)
malloc申请内存后,应该判断是否申请成功
free 只能释放申请到的内存,且不可多次释放
5、小结
堆空间是程序中预留且可用的内存区域
void*指针只能能够保存地址,但无法获取内存数据
void*指针可与其它数据指针相互赋值
malloc申请内存后,应该判断是否申请成功
free只能释放申请到的内存,且不可多次释放
6、一些注意事项
可以定义指向指针的指针(保存指针变量的地址)
一维数组名的类型为Type*(变量地址类型)
二维数组名的类型为Type(*)[N](数组地址类型)
不要从函数中返回局部变量/函数参数的地址

