第二章-基本数据类型
1、为什么1个字节的存储空间,对于有符号整数,最小值为-128
在计算机中,存储的都是补码。对于有符号整数,计算机用最高位表示正负。第一个位(最高位)就不能算,也就是说只有7位, 即2^7=128 个数字,一共正负各128种状态,共256个状态。如果不采用特殊处理,这时候0占用2个编码(10000000和00000000),数据表示范围为-127到-0及+0到127,这样总体上一个字节只有255种状态,因为其中0具有正0和负0之分,这不符合数学意义也浪费一个编码。所以计算机规定,如果是正0,也就是说遇到00000000这个编码(正0)就算它为0,如果遇到10000000(负0)这个编码会特殊处理一下,先取反:01111111,再加1:10000000。此时最高位还是1,代表负数定义,同时原先表示-0的编码被利用起来表示-128
对于-a ,其补码就是0-a,实际是2^n-a,n是这种类型的位数
2、以一个字节为例:00000000->0
11111111~10000000 -> -1 ~ -128
00000001~01111111 -> 1 ~ 127
数的范围:- ~
-1 n为二进制位数
3、截断:整型提升:char和unsigned char/int 以无符号输出:让你秒懂对char和unsigned char 以%d或%u形式打印的核心_unsigned char打印-CSDN博客
4、科学计数法 尾数部分只能是整数
第三章-C运算符和表达式
1、求余运算限定参与运算的两个操作数必须为整型
2、++,--运算操作数必须是变量,不能是常量或表达式
3、宏定义不是C语句,而是一种编译预处理命令
4、float型数据在运算时一律转换为双精度型,以提高运算精度
5、误以为(float)m 这种强制运算可以改变变量m的类型和数值,并不会!
强制类型转换的优先级高于四则运算
6、sizeof()是静态运算符,()表达式不会运算
7、大小写转换:大写->小写 Z+'a'-'A' 小写->大写 z+'A'-'a'
第四章-键盘输入和屏幕输出
1、当转义序列出现在字符串中时,是按单个字符计数的
2、%s,输入字符串,遇到空白字符(包括空格,回车,制表符),系统认为读入结束
3、scanf在输入数值型数据时,遇:空格符,回车符,制表符,达到输入域宽,非法字符 会认为数据输入结束
4、在用%c格式读入字符时,空格字符和转义字符(包括回车)都会被当做有效字符读入
5、a.%c格式符存在问题解决方法:getchar()将数据输入时存入缓冲区中的回车符读入,以避免被后面的字符型变量作为有效值输入
b.在%c前面加一个空格,忽略前面数据输入时存入缓冲区中的回车符。加空格可以识别空格,
6、对于printf,任何小于int的类型会被转换为int;float会被转换为double。但是scanf不会
第五章-选择控制结构
1、为什么不直接将实数与0比较:浮点数在内存中存储时其尾数所占的位数是有限的,因此其所能表示的实数的精度也是有限的。浮点数并非真正意义上的实数,只是其在某种范围内的近似。因此只能用近似的方法将实数与0进行比较。
两个浮点数相比较,可能失败。比如说float a=1.345f b=1.123f c=a+b c!=2.468
#define EPS 1e-6
if(fabs(disc-0)<=EPS
2、switch(表达式),表达式只能是char型和int型
3、C语言规定赋值表达式中的左值必须是变量
4、!的优先级>&&> || !优先级蛮高
5、scanf()调用成功(能正常读入输入数据),则其返回值为已成功读入的数据项数,scanf函数并不做参数类型匹配检查,可以检查:输入格式错误,存在非法字符,无数据可读
6、&:对字节中的某位清零
|:对字节中的某位置1
~:加密处理
<<:*2
>>:/2
第六章-循环控制结构
1、利用rand()%b+a 将随机数的取值范围平移到[a,a+b-1]上
2、srand(time(NULL)) 设置随机数种子
3、辨析break 和 continue
4、break 不能挑出多重循环
第七章 数组和算法基础
1、定义但未初始化的数组元素的值是随机值
2、当数组在所有函数外定义,或用static定义为静态存储类型时,自动初始化为0.-编译阶段完成
3、数组名--首地址。因此用数组名作函数实参实际上是将数组的首地址传给被调函数。传入后,形参和实参数组实际上占用的是同一段存储单元。
4、数组第二维的声明不能省略。(C语言中的二维数组元素在C编译程序为其分配的连续存储空间中是按行存放的)
5、二分中,mid=low+(hijg-low)/2
第八章 字符串
1、"a"是字符串字面量 ‘a'是字符常量
2、字符串结束标志’\0' 也占一个字节的内存,但不计入字符串的实际长度,只计入数组长度
3、字符串保存在只读的常量存储区中,所以此时可修改指针变量的值,但不能对指针所指向的存储单元进行写操作,如:char *ptr; ptr="hello"; ptr='w' ; 是错误的。
但如果是字符数组char str[10]="hello"; char *ptr = str; *ptr='w' 是正确的 相当于str[0]='w'
所以:分辨字符数组和字符指针
字符数组,由若干元素组成,str数组本身是地址常量,它的值不能修改(即地址不能修改),但是str的内容可以修改,指向它的指针也可以修改
字符指针,存放的是地址,ptr是指针,指向可以修改,但内容不可以修改,因为是常量
4、*(ptr+i)=*(str+i)=str[i]
5、scanf("%s",str); 遇到空白符(空格,回车符,制表符)结束,所以%s部署输入带空格的字符串,因为数组名本身就表示首地址,所以不用再取址;
6、gets()和scanf()的区别:gets()以回车符作为字符串的终止符,同时将回车符从输入缓冲区读走,但不作为字符串的一部分。而scanf()不读走回车符,回车符仍留在输入缓冲区中
7、puts()用于从括号内的参数给出的地址开始,依次输出存储单元中的字符,当遇到第一个'\0'时输出结束,并自动输出一个换行符。
8、char *ptrname ; gets(ptrname); 错误! ptrname尚未指向一个确定的存储单元,非法内存访问错误!
9、字符串赋值只能使用strcpy(); 比较字符串应使用函数strcmp()
10、因为字符数组和字符指针都可以存取C字符串,因此,向函数传递字符串时,既可以使用字符数组作函数参数,也可以使用字符指针作函数参数
11、char (*f)(); 该指针变量指向一个返回值为字符型的函数
12、const位置 int a,b;
const int *p=&a; == int const *p =&a; *p无法修改,但p=&b合法
int* const p=&a; *p=20 合法,但p=&b 非法
const int* const p=&a; 都非法
13、字符串“123”在内存中的存储形式:
字符 | '1' | '2' | '3' | '\0' |
ASCII码值 | 49 | 50 | 51 | 0 |
二进制值 | 00110001 | 00110010 | 00110011 | 00000000 |
第九章 指针
1、内存中的地址都是按字节编号的,即内存的每个字节的存储单元都有一个地址
2、int *p=&a 不能理解为将&a的值赋给pa所指向的变量。而是:定义一个可以指向整型数据的指针变量pa,并用整型变量a的地址对指针变量pa进行初始化,从而使指针变量pa具体地指向了整型变量a
3、函数指针:
void Selection(int a[], int n, int (*compare)(int a, int b) );//声明
int Ascending(int a, int b); //声明
Selection(score , n , Ascending);//调用
void Selection(int a[], int n, int (*compare)(int a, int b) ){ //定义
...
if( (*compare)( a, b) )....
}
int (*compare)(int a, int b) compare是一个指针变量,该指针变量可以指向一个两个整型形参,返回值为整型的函数,即compare是一个函数指针
int *compare(int a, int b)声明的不是一个指针,而是一个两个整型形参并返回整型指针的函数
4、指针的运算
类型 | 运算结果 |
指针与整数的加减运算 | 指针当前指向位置的前方或后方第n个数据的地址 |
指针+1,-1运算 | ++指向下一个数据的位置,--反之 |
指针的相减 | 结果值是整数,该值表示这两个指针所在地址之间的数据个数(指针变量相加没有意义) |
指针的关系运算 | 比较前后关系(不同数据类型的指针之间的关系运算没有意义) |
第十章 指针和数组
1、a+i = &a[i] a[i] = *(a+i)
2、假设定义了一个指向整型数据的指针变量p,并使其值为数组a的首地址。
p = a -> p = &a[0]
&p[i] = p+i
p[i] = *(p+i)
注意,p+1和p++的区别。
p+1并没有对p进行赋值操作,所以p+1并不改变当前p的指向。
p++,相当于执行p=p+sizeof(p的基类型)//字节,因此p进行了赋值操作,指向下一个元素。
3、一维数组作函数形参时,因为它只是起到接受数组起始地址的作用,所以会发生数组类型到指针类型的隐式转换,即使将形参声明为一维数组,它也将退化为指针,系统仅仅为其分配指针作战的内存空间,并不为形参数组分配额外的存储空间,而是让形参数组共享实参数组所占的存储空间。
4、数组和指针并非在所有的情况下都是等同的,例如:sizeof(数组名),sizeof(指针变量名) 无论何种数据的指针变量,占用的内存大小是相同的,例如char *p1,double *p2,其sizeof(p1),sizeof(p2)均为4字节
5、定义一维数组 int a[10] ,数组名a代表的是该数组首元素的首地址,而不是数组a的首地址,&a才是整个数组的首地址。a与&a[0]的含义相同。a==&a[0],返回真,而&a==&a[0]是错误的比较,尽管他们的地址相同,但两者的含义不同。
定义二维数组 int a[][n] ,数组名a代表的是该二维数组首元素的首地址,而不是数组a的首地址,&a才是这个数组的首地址。a==&a[0][0]是错误的,&a==&a[0]也是错误的
6、*p++ :*p(返回其值) , p++
*++p :++p , *p(返回其值)
(*p)++ : *p(返回其值),然后将*p的结果+1
7、二维数组地址:行地址a+i ==&a[i] 列地址a[i]+i i表示的是字节数,所以一行所占字节数为4*sizeof(int)
a[0]可看作由元素a[0],a[1]...组成的一维数组的数组名,表示a[0][0]的地址
a[i]+j 即*(a+i) + j 表示这个数组中下标为j的元素地址,即&a[i][j]
*(a[i]+j) 即 *(*(a+i)+j) 代表这个地址所指向的值,即a[i][j]
因此,二维数组,数组名a可以看出一个行地址,a[i]可以看成一个列地址
8、行指针和列指针
行指针:int (*p)[4]; 定义了一个可指向含有四个元素的一维整型数组的指针变量。
p[i][j]==*(p[i]+j)==*(*(p+i)+j)==(*(p+i))[j]
列指针:int *p;
p=a[0] == p=*a == p=&a[0][0]
p代表数组的首地址,*(p+i*n+j)==p[i*n+j]==a[ij[j]
9、列指针++ 每次移动的字节数为二维数组的基类型所占的字节数。二维数组的列指针常用作函数参数,以实现当二维数组的行列数需要动态指定的场合
10、行指针++ 每次移动的字节数为二维数组的列数*数组的基类型所占的字节数
11、指针数组:一个数组,指针作为数组的元素
char *ptr[]; 注意,在使用指针数组之前必须先对数组元素初始化
指向数组的指针:指针变量,指针变量里保存的是一个数组的首地址
12、通过移动字符串在实际物理存储空间中的存放位置而实现的排序称为物理排序。而用指针数组存储每个字符串的首地址时,字符串排序时无需改变字符串在内存中的存储位置,只要改变指针数组中各元素的指向即可,这种通过移动字符串的索引地址实现的排序,称为索引排序
13、指针数组用于表示命令行参数
14、动态内存分配函数<stdlib.h>
void *malloc(unsigned int size); 分配若干字节的内存空间,返回一个指向该内存首地址的指针
void *calloc(unsigned int num , unsigned int size); 给若干同一类型的数据项分配连续的存储空间并赋值为0,返回值为数组的首地址
void free(void *p); 释放向系统动态申请的由指针p指向的存储空间
void *realloc(void *p, unsigned int size); 改变原来分配的存储空间的大小,返回值是新分配的存储空间的首地址
15、动态局部变量都是在栈上创建内存的,在函数调用结束后就被自动释放了。当指针指向的栈内存被释放以后,指向它的指针并未消亡。内存被释放后,指针的值(即栈内存的首地址)其实并没有改变,它仍然指向这块内存,只不过内存中存储的数据变成了随机值。释放内存的结果只是改变了内存中存储的数据,使该内存存储的内容变成了垃圾。指向垃圾内存的指针,称为野指针。当然,内存被释放后,指向它的指针不会自动变成空指针。
16、形参不能返回函数中动态分配的内存首地址给实参。但可以利用return 返回动态分配的内存首地址给主函数,不会造成野指针的问题。因为动态分配的内存不会在函数调用结束后被自动释放,必须使用free
17、缓冲区溢出攻击
第十一章 函数与模块化程序设计
1、函数的返回值的类型不能是数组
2、全局变量的作用域为整个程序。从程序运行开始就占据内存,仅在程序结束时才将其释放。全局变量在不指定初值时会自动初始化为0、
3、并列的语句块内定义的同名变量互不干扰,占据不同的内存单元
4、自动变量(动态局部变量) - 动态存储区分配内存 - auto int temp
进入语句块时自动申请内存,退出语句块时自动释放内容
自动变量在定义时不会自动初始化
5、静态变量 - static int temp; - 在静态存储区分配内存,生存周期时整个程序运行期间 -
静态局部变量作用域为函数内,退出函数后不会被释放,记忆功能
静态全局变量可以在定义它的文件内的任何地方被访问,但不能像非静态的全局变量被程序的其他文件所访问
6、外部变量 - extern int temp - 在静态存储区内分配内存,生存周期为整个程序运行期间
如果在所有函数之外定义的变量没有指定其存储类别,它就是外部变量。
它时全局变量,作用域是从它的定义点到本文件的末尾。但是如果要在定义点之前或者在其他文件中使用它,就要用extern声明
没有显式初始化的外部变量由编译器自动初始化为0
7、寄存器变量 - register int temp -用寄存器存储的变量
将使用频率较高的变量声明为register,可以避免CPU对存储器的频繁数据访问,使程序更小、执行速度更快
8、模块化设计与多文件编程
C编译器会单独编译每一个源文件分别生成一个目标代码,然后在链接阶段将这些目标代码同标准函数库中的函数链接在一起,形成可执行文件。
每个模块均由一个.c源文件和.h头文件构成。一般地,把需要共享的函数放在一个单独的.c文件中,把共享函数的函数原型,宏定义,全局变量声明等放在一个单独的.h头文件中,其他需要共享这个函数的程序用#include包含这个头文件后就可以调用函数了。
头文件里对全局变量的声明要加上extern,对于变量声明,编译器不对其分配内存
在集成开发环境中运行多文件程序,需要将所有的.c文件都加入到当前项目中。编译器会按照项目文件的指引,把各个.c文件分别编译为同名的.obj目标文件,再将目标文件链接到一起,最后生成.exe可执行文件
第十二章 结构体和数据结构基础
1、抽象数据类型:一种数据类型,不再单纯是一组值的集合,还包括作用在值集上的操作的集合,即在构造数据类型的基础上增加了对数据的操作,且类型的表示细节及操作的实现对外是不可见的。抽象数据类型可达到更好的信息隐藏效果,因为它使程序不依赖于数据结构的具体实现方法,只要提供相同的操作,换用其他方法实现时,程序无需修改,这个特征对系统的维护很有利。
2、结构体模型只是声明了一种数据类型,定义了数据的组织形式,并未声明结构体类型的变量,因而编译器不会为它分配内存
3、当结构体模块和机构提变量放在一起定义时,结构体标记可选
4、C语言允许对具有相同结构体类型的变量进行整体赋值,但并非所有的结构体成员都是可以使用赋值运算符来赋值,对字符数组类型的结构体成员赋值时,必须使用字符串处理函数strcpy()
5、结构体变量的地址是结构体变量所占内存空间的首地址,而结构体成员的地址值与结构体成员在结构体中所处的位置及该成员所占内存的字节数相关
6、内存对齐:为了满足处理器的对齐要求,可能会在较小的成员后加入补位,从而导致结构体实际所占内存的字节数会比我们想象的多一些字节
7、结构体指针 :STUDENT *pt =stu STUDENT *pt = &stu[0] pt+1 指向的是下一个结构体数组元素stu[1]的首地址
8、共用体:将不同类型的数据组织在一起共同占用一段内存。共用体是从同一起始地址开始存放成员的值,即共用体中不同类型的成员共同用一段内存单元,因此必须有足够大的内存空间来存储占据内存空间最多的那个成员,所以共用体类型所占内存空间的大小取决于其成员中占内存空间最多的那个成员变量。共用体使用覆盖技术来实现内存的共用
9、共用体不能进行比较操作,也不能作为函数参数
10、采用共用体存储程序中的逻辑相关但情形互斥的变量,使其共享内存空间的好处是除了可以节省内存空间外,还可以避免因操作失误引起逻辑上的冲突。
11、枚举类型:枚举标签后面花括号内的标识符代表枚举型变量的可能取值,但其值是整型常数,不是字符串。
第十三章 文件操作
1、文件操作使用硬盘或u盘等永久性的外部存储设备来存储数据。
2、C语言中,文件类型:文本文件,二进制文件
文本文件:每一位数字都单独占用一个字节的存储空间。可以很方便得被其他程序读取,便于对字符进行逐个处理,便于输出字符,但一般占用的存储空间较大,且需花费ASCII与字符间的转换时间
二进制文件:把整个数字作为一个二进制数来存储,并非数值的每一位数字都占用单独的存储空间。可节省外存空间和转换时间,但一个字节并不对应一个字符,不能直接输出其对应的字符形式。
3、文件的写入和读出必须匹配,两者约定为同一种文件格式,并规定好文件的每个字节是什么类型和什么数据
4、文件系统:缓冲型,非缓冲型
5、打开文件: FILE *fp=fopen("路径",“打开方式”);
6、关闭文件: int fclose(FILE *fp);
7、读写文件中的字符: int fgetc(FILE *fp); -从fp所指的文件中读取一个字符,并将位置指针指向下一个字符,若读到文件末尾,则返回EOF 判断是否读到文件末尾:(fp = fopen(....)) ==null 或者feof(fp)
8、将字符写到文件上: int fputc(int c , FILE *fp)
9、feof()总是在读完文件所有内容后再执行一次读文件操作(将文件结束符读走,但不显示)才能返回真值
10、读写文件中的字符串:char * fgets(char *s , int n , FILE *fp); 读取失败返回null。从指定的流读字符串,读到换行符时将换行符也作为字符串的一部分读到字符串中
将字符串写入文件:int fpurs(const char *s , FILE *fp); 若写入失败,返回EOF。不会在写入文件的字符串末尾加上换行符
11、fscanf(),用于按指定格式从文件读数据
12、按数据块读取文件:fread(...),从fp所指的文件中读取数据块并存储到所指的内存中,函数返回的是实际读取到的数据块个数
13、按数据块写文件:fwrite(...),将buffer所指向的内存中的数据块写入fp所指的文件,返回的是实际写入的数据块个数