1 C语言概述
这一章无重点内容。
1.1 ASCII
第一部分是:ASCII非打印控制字符;
第二部分是:ASCII打印字符;
第三部分是:扩展ASCII打印字符
ASCII表上的数字0–31分配给了控制字符,用于控制像打印机等一些外围设备。例如,12代表换页/新页功能。此命令指示打印机跳到下一页的开头。(参详ASCII码表中0-31)
记忆:48-0;65-A;97-a;先大写后小写
特殊字符解释
2 数据类型、运算符与表达式
2.1 标识符
标识符:用来标识常量、变量、函数等字符序列;
组成:
- 只能有字母、数字、下划线组成,且第一个字符必须是字母或下划线
- 区分大小写
2.2 关键字
关键字:C语言中已经被预先定义并使用的标识符,总共有32个(下文罗列关键字带注释为不理解的部分,需要另外花时间)
数据类型关键字:
- void
- char
- float
- double
类型修饰符
- short
- long
- sighed
- unsighed
复杂类型关键字
- struct
- union:共用体声明
- enum:枚举声明:
https://blog.csdn.net/xingjiarong/article/details/47275971
- typedef:声明类型别名
- sizeof:得到特定类型或特定类型变量的大小
存储级别关键字
- auto:指定为自动变量,由编译器自动分配及释放,通常在栈上分配
- static:作为静态变量,分配在静态变量区,修饰函数时,指定函数作用域为文件内部
- register:寄存器变量
- extern :指定对应变量为外部变量,提示编译器遇到此变量和函数时在其他模块中寻找其定义:
https://blog.csdn.net/xingjiarong/article/details/47656339
- const:与volatile合称“CV特性”,指定变量的值不可被当前线程/进程改变
https://blog.csdn.net/xingjiarong/article/details/47282255
- volatile:指定变量的值有可能会被系统或其他进程/线程改变,强制编译器每次从内存中取得该变量。
A跳转结构
- return
- continue
- break
- goto
B分支结构
- if
- else
- switch
- case
- default
C循环结构
- do
- for
- while
特殊的标识符:他们没被列入关键字,却也有特定的含义。
如:define、include、under、if
new的用法:
使用new运算符时必须已知数据类型,new运算符会向系统堆区申请足够的存储空间,如果申请成功,就返回该内存块的首地址,如果申请不成功,则返回零值。
一般使用格式:
格式1:指针变量名=new 类型标识符;
格式2:指针变量名=new 类型标识符(初始值);
格式3:指针变量名=new 类型标识符 [内存单元个数];
int *a=new int; int *a = new int(5); 作用同上,但是同时将整数空间赋值为5 int *a=new int [5];
2.3 整型常量
- 了解各个进制的表示范围
- 0开头八进制,0x开头16进制(八进制不能出现>8)
2.4 实型常量
这类题易错重点关注
表示方式:
- 十进制表示形式:
- .123
- 123.0
- 0.0
- 123.
- (*)指数表示形式(e或E之前必须有数字,且指数必须为整数:指数是e后面的哦!)
- 12.3e3:12.3*10^3
- 123e2:123*10^2
- 78e-1:78*10^-1
2.5 字符型常量与字符串常量
区别
- 形式上:单双引号的区别,并且字符串尾后者多一个’\0’,(区分问法是所占空间还是长度)
- 赋值
//字符串数组,只能在定义的时候,通过=赋初值,如果先定义,在赋值就不可以 { 1. 定义的时候直接用字符串赋值 char a[10]="hello"; //注意:不能先定义再给它赋值,如char a[10]; a[10]="hello";这样是错误的。 2. 对数组中字符逐个赋值 char a[10]={'h','e','l','l','o'}; //字符串可以赋值给字符指针变量,或者将字符串用字符数组保存。 3. 先定义,后利用strcpy赋值 char a[10]; strcpy(a, "hello"); //strcpy是一种C语言的标准库函数,strcpy把含有'\0'结束符的字符串复制到另一个地址空间,返回值的类型为char*。 //函数实现还是通过数组一个个操作赋值。 } //单字符赋值,可通过数组下标方式或指针方式,引用数组元素,进行 { char a[10]; char ch='a'; for(i=0;i<10;i++) a[i]=ch+i ; //可通过数组下标引用数组元素,完成赋值 char *p; for(p=a; p<a+10; p++) *p=ch++; //通过指针对数组相应元素赋值 }
2.6 (*)不同变量所占空间
在不同及其中所占空间可能有不同,但大小关系(char1<int2<=short2<=long4<=float4<double8)
2.7 (*)运算符的优先级
3.9 位运算符与位运算表达式
- 逻辑运算符:!优先于&&优先于||
- 位运算符
按位非(取反)~
&
|
按位异或:^
右
左移:右边低位补零
int a=15,x; x=a<<2;//求x a:00001111 a<<2: 00111100
- 右移:>>
若a是有符号数,则高位补符号位
若a是无符号数,则高位补0
char a=-4,b=4,x,y; x=a>>2; y=b>>2; a:11111100(负数的补码形式不要忘) a>>2:11111111,十进制为-1;
2.10 数据类型转换
自动转换:将复制运算符的右边数据转换为左边变量的数据类型,然后赋值
3 格式化输入输出语句
许多小细节,令人抓狂
3.1 printf
1 打印整数
d格式符:用来输出十进制(decimal)
- md:输出宽度占m个字符
若小于m,左端补空格,若大于m,则按照实际位数输出
-md为左对齐
- +d:数据带有正负号
- 0md:如有前导空格,以0补齐(注意是在前面哦)
- ld:输出长整型数据,同样可以指定宽度
- *:动态域宽设置
int y=-3456; printf("%8d\n",y);//1 printf("%+8d\n",y);//2 printf("%08d\n",y); printf("y=%-8d",y); printf("y=%-08d\n",y); //output -3456 -3456 -0003456 y=-3456 y=-3456 //在devc++中跑完之后发现,这里添加1,2两句效果相同;期初我认为+是不是不起作用了,将y的数值改为正数可发现1式无+,2式有+ //最后两句都有4个空格
/*动态域宽设置*/ int i=1; printf("##%*d\n",i,i); i++; printf("##%*d\n",i,i); i++; printf("##%*d",i,i); //output ##1 ## 2 ## 3 /*注意这里的*不是指空格的数量,如其名动态域宽设置,设置的为其域宽,当i等于1时其宽度为1*/
o格式符:以八进制(octal)形式输出整数
在前面添加#号,可以使数字按照八进制(0。。。)的形式输出
x格式符:以十六进制(hexadecimal)形式输出整数
在前面添加#号,同理
u格式符:以无符号(unsigned)十进制数输出
注:以上长整型,同样加’l’
//十六进制同理 int y=123; printf("y=%8o\n",y); printf("%#8o",y);
short y=-1; printf("unsigned=%u",y); //output unsigned=4294967295; //不难发现,负数在计算机内以补码形式存储,所以输出成无符号位的数字,需要把原先二进制转化为10进制
/*3.42 turb C 标准,16位微机中int=short=2byte */ int x,y,z; long m; y=(x=32767,x-1);//逗号运算符从左往右 z=m=0xffff; //output y=32766; z=-1; m=65535; //int两个字节,16——>二进制(11111111),计算机中有符号表示为-1,如果int输出为%u,同样是65535;
2 打印浮点数
f格式符
- mf:
- m.nf
整个输出占m个字符宽度,右对齐(若超出则按原样输出)
小数部分截短至n个字符
可直接用.nf
- 同样可以在前面添加**+,-,0**
注意点:
- 默认小数点后六位
- 单精度精确到8位,双精度精确到16位。
- float和double都会被转换成double然后送给printf函数 所以其实用%f还是%lf输出其实并不重要,输入时,%lf表示地址对应的是8字节的double,%f表示地址对应的是4字节的float……存储方式都不一样,混用了肯定会出问题
printf("%f",1.12345678); //实际输出为1.123457,因为保留6位有效数字,所以第七位要四舍五入到第六位。 printf("%.18f\n",1.12345678901234567890); //下方实数末尾的f表示单精度浮点数,只存储7位有效数字 printf("%.18f\n",1.12345678901234567890f);
g格式符:(自动选择)
采用g格式符输出数据时
- 当数据<=10-5或>=107时,按指数方式输出
- 否则按小数方式输出
double a=1000000000; double b=123.456; printf("%e\n",a); printf("%f\n",a); printf("%g\n",a); printf("%e\n",b); printf("%f\n",b); printf("%g\n",b);
e格式符:以十的指数形式输出
3 打印字符、字符串
c格式符:用来输出一个字符(character)
printf("\n*s1=%15s*","chinabeijing"); printf("\n*s21=%-5s*","chi"); //output *s1= chinabeijing* *s21=chi * /*凡是设置了字符宽度,默认情况下就是右对齐,若前面有负号才改为左对齐*/
s格式符:用来输出一个字符串(string)
- .ms表示只输出字符串的前m个字符
3.2 (*)scanf
1格式
scanf(格式控制字符串,输入表列);
2 用法
输入字符的情况:
/*用法有三种,最易错的为%c*/ { char c1,c2,c3; scanf("%c%c%c",&c1,&c2,&c3); } //若此时输入a_b_c; //output a b /*原因在于scanf会把空格当成字符进行存储*/
遇到字符与数字一同是输入内容的情况:
- 数字之间要用空格
- 数字与字符之间,不能用空格
输入浮点数的情况:
{ float f; scanf("%f",&f); double b; scanf("%lf",&d); } /*float和double都会被转换成double然后送给printf函数 所以其实用%f还是%lf输出其实并不重要; 输入%lf表示地址对应的是8字节的double,%f表示地址对应的是4字节的float……存储方式都不一样,混用了肯定会出问题*/
输入中带*情况的处理:
{ scanf("%d%*d%d",&a,&c); //计算机读入三个数,把第一个与第三个分别存入a和c //多了*表示读入但不赋值给变量 }
4 逻辑运算和判断选取控制
4.1 逻辑表达式
C的真值表示
- 真:1,非0数看作“真”
- 假:0
短路现象
- 计算表达式1&&表达式2 若表达式1为假,则能确定逻辑计算结果为假,表达式2不继续进行
- 计算表达式1||表达式2 若表达式1为真,则能确定逻辑计算结果为真,,表达式2不继续进行
优先级
- !优先于&&优先于||
- 一般来说优先级为:从高到低(算数、关系、逻辑、赋值)
- 特例:!优先于二目运算(原因在于!看作单目运算符)
4.2 条件运算符和条件表达式
表达式1?表达式2:表达式3
/*这个例子带你理解条件运算符从右到左是如何进行的*/ int a=1, b=3, c=2, d; d = a>b ? a : c>b ? c : b; // 根据从右到左的结合顺序,则该表达式等价于d = (a>b ? a : (c>b ? c : b)); // 计算过程: // 1、先算a>b,结果为0; // 2、再算(c>b ? c : b),之后算c>b,结果为0, // 3、所以最后结果为b的值,也即等于3 // 如果是从左到右的结合顺序,那么该表达式等价于d =((a>b ? a : c>b) ? c : b); //计算过程: // 1、先算a>b,结果为0; // 2、再算c>b,结果为0; // 3、所以d=b,结果为b的值,也即等于3
5 函数
5.1 函数的定义
//无参类型 void main() //类型标识符 函数名 { }
//有参函数形式1 int max(int a,int b)//(类型名 形参) { int z; z=a-b; return (z); } //有参函数形式2 int max(a,b) //(形参1,形参2) int a,b; //形参类型说明 { int z; z=a-b; return (z); }
//空函数定义形式 int dummy { }
5.2 函数调用
5.2.3函数的参数
- 形参:出现在函数定义的括号后面
- 实参:出现在调用者的括号后面
实际参数通过函数调用的过程,将真实的值赋值给形式参数(里面的操作可能会对实际参数造成影响,绝大多数也可能没影响)
//交换两个数的一些好方法(不通过函数) //方法1:使用一个临时变量来进行保存 { temp = a; a = b; b = temp; } //方法2:使用加减法来进行交换,这种方法实际上就是先把a+b的结果暂时先保存在变量a中,然后通过这改变后的a和原始的b进行减法就可以得到交换后的b,但是这种方法有一个缺陷,就是a和b都是int类型,a+b的结果可以越界。 { a = a + b; b = a - b; a = a - b; } //方法3:使用乘除法来进行交换,这种方法和方法2类似,只是用a*b代替a+b放在a中暂存,后面思路一样,这种方法有着和方法2一样的缺陷,就是越界问题,而且更加容易越界。 { a = a * b; b = a / b; a = a / b; } //(妙)方法4:使用异或的方法来进行交换,这种方法不存在之前越界的缺陷,是一种很完美的方法,这种方法主要利用了异或的特性a^b的值先保存到a,因为a^b^b=a所以可以顺利完成交换。 { a = a ^ b; b = a ^ b; a = a ^ b; } //方法5:使用移位的方法,把原来的int类型的a,把a的值拆分成高16位和低16位,相当于多了16位的暂存空间可以周转,但是此方法的缺陷也显而易见,就是如果被赋值的a或者b超过了16位,这种方法就会发生错误。 { a <<= 16; a |= b; b = a >> 16; a = a & 0xffff; }
等到指针部分可以有一些在函数体内完成对主函数内的元素进行交换。
5.2.4 函数的返回值
- return:使函数中断,程序流程返回到主调函数,不返回值
- return(表达式):中断,返回一个值给主调函
函数调用时,注意实参表求值的顺序并不确定,有的是从左到右,有的是从右至左,C-free按自右向左,因此要避免出现歧义的情况,这在群里争议过的题目中有体现!
关于函数调用时,实参求值顺序的一道题
int f(int a,int b) { int c; if(a>b) c==1; else if(a==b) c=0; else c=-1; return (c); } int main() { int i=2,p; p=f(i,++i); printf("%d",p); } //输出为0 int f(int a,int b) { int c; if(a>b) c=1; else if(a==b) c=0; else c=-1; return (c); } int main() { int i=2,p; p=f(i,i++);//改为i++ printf("%d",p); } //输出为1
5.2.5 对被调函数的声明
- 库函数的声明
#include<stdio.h> #include"math.h" //两者的差异需知晓,如果使用下方形式,则先在当前源文件所在目录进行寻找
- 用户定义的函数声明
应用场景:主要是函数定义出现在了函数调用之后,所以要先对被调函数进行声明
//三种形式 int fun;//无参 int fun(int,char,int);//有参数,只含参数类型 int fun(int a,char c,int b);//有参数,含参数类型和参数名
5.3 (*)函数的递归调用
递归调用属于嵌套调用,但是我这里着重理解下递归调用
定义:间接或直接调用自身函数的递归调用
//递归调用求n! int fac(int n) { int f; if(n==1||n==0) //递归的终结条件; f=1; else f=n*fac(n-1); return f; } int main() { printf("%d",fac(4)); return 0; }
递归要求:
递归定义必须是有确切意义的,
每经过一步可以变得更简单,规模要变小,
最终要能够终结,不能无限调用,
每次调用都需要判断能否达到结束条件。