学习笔记
C语言程序设计
高三毕业生已经开始看网课了!刷了C语言程序设计的课,那就做个笔记吧。nga~
参考资料:C语言程序设计(笔记也是按着课程的顺序来的~,部分图片)
- 推荐的编程软件:Dev C++
一.程序设计与C语言
程序框架
#include <stdio.h>
int main()
{
return 0;
}
输出
例如printf("你好!\n");
- " "里面的内容叫”字符串“,printf会把里面的内容原封不动地输出
- \n表示在需要输出的结果后面换一行
- \t表示使输出的内容对齐
做计算
例如printf("%d\n", 23+43);
- %d说明后面有一个整数要输出在这个位置上
四则运算 | C符号 | 意义 |
---|---|---|
+ | + | 加 |
- | - | 减 |
× | * | 乘 |
÷ | / | 除 |
% | 取余 | |
() | () | 括号 |
- %表示与两个数相除以后的余数
注
单行的注释可用“//”,延续数行的注释,要用多行注释的格式来写。多行注释由一对字符序列“/ * ”开始,而以“ * /”结束。
二.计算
示例:
#include <stdio.h>
int main()
{
int price = 0;
printf("请输入金额(元):");
scanf("%d", &price);
int change = 100 - price;
printf("找您%d元。\n", change);
return 0;
}
变量
变量定义
- 变量定义的一般形式:<类型名称><变量名称>;
-int price;
-int amount;
-int price,amount;
变量的名字 - 变量需要一个名字,变量的名字是一种”标识符“
- 标识符有标识符的构造规则。基本的规则是:标识符只能由字母,数字,下划线组成,数字不可以出现在第一个位置上。C语言的关键字(也叫保留字)不可以用作标识符。
- C语言的保留字
- auto,break,case,char,const,continue,default,do,double.else,enum,extern,float.for.goto,if,int,long,register,return,short,signed,sizeof,static.struct,switch,typedef,union.unsigned,void,volatile,while,inline,restrict
赋值和初始化
例如:`int price = 0
- 这一行,定义了一个变量,变量名字是price,类型是int,初始值是0.
- price = 0是一个式子,”=“是一个赋值运算符,表示将”=“右边的值赋给左边
赋值:a=b表示要求计算机执行一个动作,程序设计中,a=b和b=a意思完全相反
初始化:当赋值发生在定义变量的时候,就是变量的初始化。
变量初始化
- <类型名称><变量名称>=<初始值>
- 例如:
int price = 0;
int amunt = 100;
- 组合变量定义的时候,也可以在这个定义中单独给单个变量赋初值,如`int price =0,amount = 100;
注:ANSI C只能在代码开头的地方定义变量;C99可在任何地方定义。
读整数 - 例如
scanf("%d",&price)
- 要求scanf这个函数读入下一个整数,读到的结果赋值给变量price
- 小心price前面的&
常量 - 例如:
int change = 100 - price;
- 固定不变的数是常数。直接写在程序里,我们称作直接量(literal),更好的方式,是定义一个常量:
const int AMOUNT = 100;
- const是一个修饰符,加载int前面用来给这个变量加上一个const(不变的)属性。
浮点数
- 带小数点的数值
- 当浮点数与整数放在一起运算时,C会将整数转换为浮点数,然后进行浮点数的运算
- double的意思是”双精度浮点数“的第一个单词,人们用来表示浮点数类型,除了double,还有float表示单精度浮点数。
整数 | 带小数的点 |
---|---|
int | double |
printf("%d",...); | printf("%f",...); |
scanf("%d",...); | scanf("%lf",...); |
对该部分知识的一些运用
#include <stdio.h>
int main()
{
int a;
int b;
printf("请输入两个整数:");
scanf("%d %d", &a, &b);
printf("%d + %d = %d\n", a, b, a + b);
return 0;
}
#include <stdio.h>
int main()
{
float foot;
float inch;
printf("请分别输入身高的英尺和英寸");
scanf("%lf %lf", &foot, &inch);
printf("身高是%f米",(( foot + inch / 12 ) *0.3048));
return 0;
}
计算时间差:
#include <stdio.h>
int main()
{
int hour1, minute1;
int hour2, minute2;
scanf("%d %d", &hour1, &minute1);
scanf("%d %d", &hour2, &minute2);
int t1 = hour1 * 60 + minute1;
int t2 = hour2 * 60 + minute2;
int t = t2-t1;
printf("时间差是%d小时%d分。", t/60, t%60);
return 0;
}
计算平均值:
#include <stdio.h>
int main()
{
int a,b;
scanf("%d %d", &a, &b);
double c = (a+b)/2.0;
printf("%d和%d的平均值=%f\n", a, b, c);
return 0;
}
计算复利:
#include <stdio.h>;
int main()
{
int x ;
scanf("%d",&x);
double y = x*(1+0.033)*(1+0.033)*(1+0.033);
printf("%f", y );
return 0;
}
注:C语言没有幂次
运算符优先级
a*-b也就是a乘以(-b)的意思
- 赋值也是运算,也有结果。
交换变量
#include <stdio.h>;
int main()
{
int a = 5;
int b = 6;
int t;
t = a;
a = b;
b = t;
printf("%d %d",a,b);
return 0;
}
用临时变量 t 作中间存放的地方
复合赋值
-
total +=(sum+100)/2;
total = total+(sum+100)/2;
-
total*= sum+|2;
total = total*(sum+|2);
-
total /=|2+6;
total = total/(12+6);
-
“++”和“-”是两个很特殊的运算符,它们是单目运算符,这个算子还必须是变量。这两个运算符分别叫做递增和递减运算符他们的作用就是给这个变量+或者-1。
-
count ++;
count +=|;
count = count +|;
-
a++和++a都是C语言中的自增运算符,但它们的执行顺序和结果不同。
-
a++表示先使用a的值,再将a的值加1。例如,若a的值为3,执行a++后,a的值变为4,但表达式的值为3.
-
++a表示先将a的值加1,再使用a的值。例如,若a的值为3,执行++a后,a的值变为4,表达式的值也为4.
如何十进制读入,十六进制输出?
#include <stdio.h>;
int main()
{
int m;
scanf("%d", &m);
printf("%x", m);
return 0;
}
“%x” 十六进制;同理 “%o” 八进制
- 一个以0开始的数字字面量是8进制
- 一个以0x开始的数字字面量是16进制
三.判断
做判断
判断时间差
int hour1, minute1;
int hour2, minute2;
scanf("%d %d",&hour1,&minute1);
scanf("%d %d",&hour2,&minute2);
int ih = hour2 - hour1;
int im = minute2 - minute1;
if( im < 0){
im = im + 60;
ih --;
}
printf("时间差为%d小时%d分钟\n",ih,im);
if语句写法:
if(条件成立){
...
} else {
...
}
为了让程序在条件成立时或者不成立时执行一个语句块,需要在if ()之后和else之后使用{}。如果在条件成立或不成立时需要执行的语句只有一条,就没有必要使用{}了。
关系运算
运算符 | 意义 |
---|---|
== | 相等 |
!= | 不相等 |
> | 大于 |
>= | 大于或等于 |
< | 小于 |
<+ | 小于或等于 |
- 当两个值的关系符合关系运算符的预期时,关系运算的结果为整数1,否则为整数0
- 所有的关系运算符的优先级比算术运算的低,但是比赋值运算的高
- 判断是否相等的==和!=的优先级比其他的低,而连续的关系运算是从左到右进行的
分支
- 嵌套:当if的条件满足或者不满足的时候要执行的语句也可以是一条if或if-else语句,这就是嵌套的if语句
- else的匹配:else总是和最近的那个if匹配,
- tips: 在if或else后面总是用{ },即使只有一条语句的时候
大括号内的语句缩进一个tab的位置
级联的 if-else if
if(exp1)
stl;
else if(exp2)
st2;
else if(exp3)
st3;
例如,分段函数 f(x)= -1,x<0;
0,x=0;
2*x,x>0
int x;
scanf("%d",&x);
int f = 0;
if( x < 0){
f = -1;
}else if( x == 0 ){
f = 0;
}else{
f = 2*x;
}
printf("%d",f);
if的常见错误
- 忘了大括号
- if后面加了分号;
- 错误使用 == 和 =;
- if只要求( ) 里的值是0或非0
- 使人困惑的else。
switch-case
switch(控制表达式){
case 常量:
语句
......
case 常量:
语句
......
default:
语句
......
}
- 控制表达式只能是整数型的结果
- 常量可以是常数,也可以是常数计算的表达式
- 根据表达式的结果,寻找匹配的case,并执行case后面的语句,一直到break为止
- 如果所有的case都不匹配,那么就执行default后面的语句;如果没有default,那么就什么都不做
四.循环
循环
while循环
while(条件成立){
...
}
- { }内的语句叫循环体
- 循环体内要有改变条件的机会
- 循环执行之前判断是否继续循环,所以有可能循环一次也没有被执行;
do-while循环
do{
<循环体语句>
}while(<循环条件>);
注意while后面的“;”。
循环应用
课程听完自己打一遍加深记忆喵
猜数游戏:
#include <stdio.h>
int main()
{
srand(time(0)) ;
int count = 0;
int number = rand()%100+1;
printf("已经随机生成一个1到100之间的数。\n");
int a;
do{
printf("请猜这个1到100之间的数:\n");
scanf("%d",&a);
count ++;
if(a>number){
printf("你猜的数大了。");
}else if(a<number){
printf("你猜的数小了。");
}
}while (a!= number);
printf("太好了,你用了%d次就猜对了正确答案。\n",count);
return 0;
}
随机数
每次召唤 rand() 就得到一个随机的整数
计算平均数:
#include <stdio.h>;
int main()
{
int number;
int sum = 0;
int count = 0;
scanf("%d",&number);
while (number != -1){
count ++;
sum += number;
scanf("%d",&number);
}
printf("%f\n",1.0*sum/count);
return 0;
}
整数逆序:
#include <stdio.h>
int main()
{
int x;
scanf("%d", &x);
int digit;
int ret = 0;
while ( x> 0 ) {
digit = x%10;
printf("%d", digit);
ret = ret*10 + digit;
// printf("x=%d,digit=%d,ret=%d\n", x, digit, ret);
x /= 10;
}
// printf("%d", ret);
return 0;
}
九九乘法表:
#include <stdio.h>
int main()
{
int x;
scanf("%d",&x);
int i = 1;
int j ;
while (i<=x){
j = 1;
while(j<=i){
printf("%d*%d=%d\t",j,i,i*j);
j++;
}
i++;
printf("\n");
}
return 0;
}
五.循环控制
for 循环
for(初始动作;条件;每轮的动作){
}
for( ;条件: ) == while(条件)
- for中的每一个表达式都是可以省略的。“;”不可以省略。
Tips for loops
- 如果有固定次数,用for;
- 如果必须执行一次,用do_while;
- 其他情况用while。
循环控制
- break:跳出循环;
- continue: 跳出循环这一轮剩下的语句进入下一轮
- 嵌套循环时的break和continue,只对它所在的那一层循环做。
注: 只有switch while 和for可以berak;switch没continue,for和while有。
跳出循环
一.接力break:
int x;
int one, two, five;
int exit = 0;
scanf("%d", &x);
for ( one = 1; one < x*10; one++ ) {
for ( two = 1; two < x*10/2; two++ ) {
for ( five = 1; five < x*10/5; five++ ) {
if ( one + two*2 + five*5 == x*10 ) {
printf("可以用%d个1角加%d个2角加%d个5角得到%d元\n",
one, two, five, x);
exit = 1;
break;
}
}
if ( exit == 1 ) break;
}
if ( exit == 1 ) break;
}
二. goto:
int x;
int one, two, five;
scanf("%d", &x);
for ( one = 1; one < x*10; one++ ) {
for ( two = 1; two < x*10/2; two++ ) {
for ( five = 1; five < x*10/5; five++ ) {
if ( one + two*2 + five*5 == x*10 ) {
printf("可以用%d个1角加%d个2角加%d个5角得到%d元\n",
one, two, five, x);
goto out;
}
}
}
}
out:
注: out的位置得自己设。
循环应用
1.整数分解:
#include <stdio.h>
int main()
{
int x;
scanf("%d",&x);
int t = x;
int mask = 1;
while( t>9 ){
t /=10;
mask *=10;
}
do{
int n = x/mask;
printf("%d",n);
if(mask > 9){
printf(" ");
}
x %= mask;
mask /= 10;
}while(mask>0);
return 0;
}
2.求最大公约数:
辗转相除法: (1).如果b等于0,计算结束,a就是最大公约数;
(2)否则,计算a除以b的余数,让a等于b,而b等于那个余数;
(3)回到第一步。
#include <stdio.h>
int main()
{
int a,b;
int t;
scanf("%d %d", &a, &b);
int origa = a;
int origb = b;
while ( b != 0 ) {
t = a%b;
a = b;
b = t;
}
printf("%d和%d的最大公约数是%d.\n", origa, origb, a);
return 0;
}
枚举:
#include <stdio.h>
int main()
{
int a,b;
int min;
scanf("%d %d", &a, &b);
if ( a<b ) {
min = a;
} else {
min = b;
}
int ret = 0;
int i;
for ( i = 1; i < min; i++ ) {
if ( a%i == 0 ) {
if ( b%i == 0 ) {
ret = i;
}
}
}
printf("%d和%d的最大公约数是%d.\n", a, b, ret);
return 0;
}
3.求前五十个素数:
#include <stdio.h>
int main()
{
int x;
int cnt = 0;
for(x = 2;cnt < 50;x ++){
int i;
int isPrime = 1;
for(i = 2;i < x;i ++){
if(x%i==0){
isPrime = 0;
break;
}
}if(isPrime == 1){
cnt ++;
printf("%d\t ",x);
if(cnt%5==0){
printf("\n");
}
}
}
return 0;
}
六.数据类型
C语言的类型
- 整数:char , short , int , long , long long
- 浮点数:float , double , long double
- 逻辑:bool
- 指针
- 自定义类型
注:黄色的是C99的类型
类型的不同:
- 类型名称:int、long、double
- 输入输出时的格式化:%d、%ld、%lf
- 所表达的数的范围:char <short <int< float <double
- 内存中所占据的大小:1个字节到16个字节
- 内存中的表达形式:二进制数(补码)、编码
sizeof 是给出某个类型或变量在内存中所占据的字节数的运算符
sizeof( )括号里的运算时不会做的
整数
char 1字节(8比特)
short 2字节
int 取决于编译器(CPU),通常的意义是“1个字”
long 取决于编译器(CPU),通常的意义是“1个字”
long long 8字节
整数的内部表达
计算机内部的一切都是二进制
二进制负数:
补码:拿补码和原码可以加出一个溢出的“零”,如:
对于一个字节,可以表达的是 00000000~11111111:
- 000000000 -> 0;
- 1111111~10000000 -> -1~128
- 00000001~01111111 -> 1~127
- 11111111 + 1 -> 100000000 -> 0
- 01111111 - 1 -> 10000000 -> -128
unsigned: 在整数类型面前加上unsigned使它们成为无符号的整数;如果一个字面量常数项表达自己是unsigned,可以在后面加u或U。初衷 :为了做纯二进制运算,主要是为了移位。
整数的输入输出
只有两种形式 int 或 long long
%d: int
%u: unsigned
%ld: long long
%lu: unsigned long long
浮点数
类型 | 字长 | 范围 | 有效数字 |
---|---|---|---|
float | 32 | ±(1.20x100,±inf,nan | 7 |
double | 64 | ±(2.2x100,+inf,nan | 15 |
类型 | scanf | printf |
---|---|---|
float | %f | %f, %e |
double | %lf | %f, %e |
e或E:科学计数法,5.67E+16即为5.67 * 10 ^ 16
输出精度:在%和f之间加上.n可以指定输出小数点后几位,这样的输出是做4舍5入的
printf输出inf表示超过范围的浮点数: ±∞
printf输出nan表示不存在的浮点数
带小数点的字面量是double而非float
float需要用f或F后缀来表明身份
- f1 == f2可能失败
- fabs(fl-f2)< le-8
字符
- char是一种整数,也是一和特殊的类型:字符。
- 用单引号表示的字符字面量:’a‘,’1‘
- "也是一个字符
- printf和scanf里用%c来输入字符
字符计算:
一个字符加一个数字得到ASCI码表中那个数之后的字符,两个字符的减,得到它们在表中的距离,如:
a+'a’-'A”可以把一个大写字母变成小写字母;
a+‘A’-'a’可以把一个小写字母变成大写字母。
逃逸字符:
类型转换
- 自动类型转换:当运算符的两边出现不一致的类型时,会自动转换成较大的类型
- 强制类型转换:
- (类型)值 ,比如: (int)10.5
逻辑运算
bool:
#include <stdbool.h>
之后既可以使用bool和true、false
逻辑运算结果只用0或1
- ! 逻辑非 !a
- // 逻辑或 a//b
- && 逻辑与 a&&b
- 表达x∈(2,3),x>2&&x<3
条件运算
count = (count>50)?count-20:count+20;
count=(条件)?条件满足时的值:条件不满足时的值;
逗号运算
- 逗号用来连接两个表达式,并以其右边的表达式的值作为它的结果。
- 逗号的优先级是所有的运算符中最低的.
七.函数
"代码复制"是程序不良的表现 so?
我们使用函数!
函数的定义和使用
函数是一块代码,接受零个或多个参数,做一个事情,并返回零个或多个值。
返回类型 函数名(参数表)
{
函数体
}
- 即使没有参数也要( )
- 如果有参数,则需要给出正确的数量和顺序
- 函数知道每一次是从哪里调用它,会返回到正确的地方
- return停止函数的执行,并送回一个值
- 一个函数里可以出现多个return
- 没有返回值的函数:void 函数名(参数表)
- 不能使用带值的return
- 使用时可以没有return
函数的参数和变量
#include <stdio.h>
void sum(int begin,int end); //声明
int main()
{
sum(1,10);
sum(11,20);
return 0;
}
void sum(int begin,int end) //定义
{
int i;
int sum = 0;
for(i = begin;i <= end;i ++){
sum += i;
}
printf("%d到%d的和是%d\n",begin,end,sum);
}
- 函数头,以" ; "结尾,就构成了函数原型,函数原型的目的是告诉编译器这个函数长什么样。
- 如果函数有参数,调用函数时必须传递给它数量、类型正确的值。
- 这些值可以是变量,字面量,函数的返回值,计算的结果。
本地变量
函数的每次运行,就产生了一个独立的变量空间,在这个空间中的变量,是函数的这次运行所独有的,称为本地变量
- 定义在函数内部的变量就是本地变量
- 变量的生存期和作用域都在大括号内(也被称为块)
注: C语言不允许函数嵌套定义。
八.数组
定义数组:
- <类型> 变量名称[元素数量];
- 元素数量必须是整数
- C99之前元素数量必须是编译时刻确定的字面量
数组
- 数组中所有元素具有相同的数据类型
- 数组中元素在内存中是连续依次排列的
- 使用数组时放在[ ]中的数字叫下标或索引,下标从0开始计数
- 一旦程序运行,越界的数组访问可能造成问题,导致程序崩溃segmentation fault
#include <stdio.h>
int main()
{
const int number = 10; //数组的大小
int count[number]; //定义数组
int x;
int i;
for(i=0;i<number;i++){
count[i] = 0;
} //初始化数组
scanf("%d",&x);
while(x!=-1){
count[x]++; //数组参与运算
scanf("%d",&x);
}
for(i=0;i<number;i++){
printf("%d:%d\n",i,count[i]);
} //遍历数组输出
return 0;
}
数组运算
数组的集成初始化:
int a [] = {2,3,4,};
- 直接用大括号给出数组的所有元素的初始值
- 如果给出了数组的大小,但是后面的初始值数量不足则其后的元素被初始化为0
集成初始化时的定位 (仅C99可用)
int a [] = {[2]=2,3};
- 用[n]在初始化数据中给出定位
- 没有定位的数据接在前面的位置后面
数组的大小
sizeof(a)/siziof(a[0])
数组在函数中作为参数时:
- 不能在[ ]中给出数组的大小;
- 不能再利用sizeof计算数组元素个数
构造素数表
#include <stdio.h>
int main()
{
const int max = 25;
int isprime[max];
int i;
int x;
for(i = 0;i < max;i ++){
isprime[i]=1;
}
for(x = 2;x < max;x ++){
if(isprime[x]){
for(i = x;i*x < max;i++){
isprime[i*x] = 0;
}
}
}
for(i = 2;i < max;i ++){
if(isprime[i]){
printf("%d\t",i);
}
}
return 0;
}
二维数组
int a[i][j]
- 表示第i行第j列上的单元
二维数组的初始化 - 列数是必须给出的,行数可以由编译器来数
- 每行一个门,逗号分隔
- 也可以用定位(only C99)
九.指针
**运算符 &😗*获取变量的地址,它的操作数必须是变量。
int p;
printf("%p",p);
地址的大小是否与int相同取决于编译器。
&不能对没有地址的东西取地址,如&(a+b);
是错误的。
那么如何保存地址呢?
指针
指针就是保存地址的变量
int i;
int* p = &i;
指针变量的值是具有实际值的变量的地址
作为参数的指针在被调用时得到了某个参数的地址,就如:
void f(int* p);
int i = 0;
f(&i);
如上,就可以通过指针访问外面的这个i
单目运算符 *
当我们想要访问地址上的变量,要用到单目运算符 *
指针应用
1.交换两个变量的值
void swap(int* p,int* q)
{
int t = *p;
*p =*q;
*q = t;
2.函数有时候返回多个值,某些值就只能通过指针返回
错误使用情况: 定义了指针变量后还没有指向任何变量就使用
传入函数的数组是什么呢?
函数参数表中的数组实际上是 指针
int sum(int* a);
int sum(int a[]);
这两种函数原型是等价的
数组变量是特殊的指针
int a[50];int*p=a; // 无需用&取地址
- 数组的单元表达的是变量,需要用&取地址
a == &a[0]
- [ ]运算符可以对数组做,也可以对指针做
- p[O] <==> a[0]
- *运算符可以对指针做,也可以对数组做
- 数组变量是const的指针,所以不能被赋值。
指针与const(仅C99)
int* const p = &i; //p是const
指针是const时,一旦得到了某个变量的地址,就不能再指向其他变量。
const int* p = &i;
int const* p = &i;
以上两条代码等价,所指是const时,不能通过这个指针去修改那个变量,但并不会使得那个变量变为const。
- 因为把数组传入函数时传递的是地址,所以那个函数内部可以修改数组的值
- 为了保护数组不被函数破坏,可以设置参数为const
int sum(const int al, int length);
指针运算
指针运算:
给指针加一表示让指针指向下一个变量,如:
int* p = a;
那么*(p+1)即为a[1]。
*优先级很高但没有++高,因此,*p++指的是取出p所指的那个数据来,完事后顺便把p移到下一个位置去。
指针比较:
<,<=,==,!=,>,>=都可以对指针做,用来比较它们在内存中的地址。
NULL是一个预定定义的符号,表示0地址。
0地址通常是个而不能随便碰的地址,可以用来做特殊的事情:
- 返回的指针是无效的
- 指针没有被真正初始化(先初始化为0)
当p = 0时,说明p指向0,这时候如果*p=某个值的话,程序就会崩溃。
指针的类型:
- 无论指向哪种类型,指针的大小都是相等的,因为都是地址
- 指向不同类型的指针不能相互赋值。
指针的类型转换: - void* 表示不知道指向什么东西的指针
- 计算时与char*相同(但不相通)
- 指针也可以转换类型
int *p= &i; void*q=(void*)p;
==注:==并没有改变p所指变量的类型,可以理解为用不同的眼光看p所指的变量。
动态内存分配
malloc函数
#include <stdlib.h>
void *malloc(size_t size);
int* a;
a = (int*)malloc(number*sizeof(int));
向malloc申请空间的大小是以字节为单位的,返回的结果是*void,需要转换为自己需要的类型。
如果没有空间了会申请失败,返回0,即NULL。
free( )
出来混迟早是要还的,通过free( )把申请得到的空间还给“系统”。
申请了没free(),长时间运行会导致内存逐渐下降。
如果程序要用到动态分配的内存,并且在函数之间传递,不要让函数申请内存返回调用者,好的模式是调用者自己申请,把地址传进函数,函数再返回这个地址出来。
十.字符串
字符串
- 以整数0结尾的一串字符,0和‘\0’是一样的但和‘0’不同,‘0’是48
- 0是字符串结束的标志,但它不是字符串的一部分,计算字符串长度的时候不包含这个0
- 字符串以数组的形式存在,以数组或指针的形式访问(更多是以指针)
- 不能用运算符对字符串做运算
- 通过数组的方式可以遍历字符串
- string.h里面有很多处理字符串的函数
- 字符串字面量可以用来初始化字符数组
字符串变量
-
char *str = "Hello";
指针str指向一个字符数组,这个数组里面放着Hello -
str是一个指针,初始化为指向一个字符串常量
- 由于这个常量所处的位置(代码段),所以实际上str是
const char* str
,但由于与历史原因,编译器不接受带const的写法 - str只读,不能对str所指字符串做写入
- 由于这个常量所处的位置(代码段),所以实际上str是
-
char word[] = "Hello";
-
如果需要修改字符串,应该用数组,如上
1)如果构造一个字符串,用数组(这个字符串在这里,作为本地变量空间自动被回收)
2)如果处理一个字符串,用指针(这个字符串不知道在哪,可以用来处理参数和动态分配空间) -
char line[10] = "Hello";
写出字符串字面量的表达形式之后,编译器会替你生成结尾的0
char*
不一定是字符串,指向字符数组结尾有0才是
字符串常量
- “Hello”会被编译器变成一个字符数组放在某处,这个数组的长度是6,结尾还有表示结束的0
- 两个相邻的字符串常量会被自动连接起来
- 行末的 \ 表示下一行还是这个字符串常量
字符串输入输出
char a[5];
scanf("%s",a);
printf("%s",a);
- scanf读入一个单词,到空格,tab或回车为止
- 在%和s之间输入一个数字,表示最多允许读入的字符的数量,数量足够不用空格,tab或回车即进入下一次scanf
char *a;
scanf("%s",a);
如果如上使用,应对a初始化为0
空字符串
char a[100] = ""; \\这是一个空的字符串,a[0]=='\0'
char a[] ="" \\这个数组长度只有1
字符串数组
char**a
a是一个指针,指向另一个指针,那个指针指向一个字符(串)char a[][]
a是一个二维数组,第二个维度的大小不知道,不能编译char a[][10]
a是一个二维数组,a[x]是一个char[10]char *a[]
a是一个一维数组,a[x]是一个char*
小练习:用字符串数组做月份名字
#include <stdio.h>
int main()
{
int number;
char *month[]={"Jan","Feb","Mar","Apr","May","Jun","Jul","Aus","Sep","Oct","Nov","Dec"};
scanf("%d",&number);
printf("%s\n",month[number-1]);
return 0;
}
字符串函数
字符串输入输出
int getchar(void);
int putchar(void);
标准库里的字符串函数
#include <string..h>
strlen 返回字符串的长度,不包括结尾的0
size_t strlen(const char*s);
strcmp 比较两个字符串,(只比较第一个不同的字符)
int strcmp(const char*s1,const char*s2);
//0:s1==s2 >0:s1>s2 <0:s1<s2
strcpy 把src的字符拷贝到dst,restrict的意思是两个字符串不重叠(C99)
char*strcpy(char*restrict dst,const char*restrict src);
复制一个字符串:
char* dst = (char*)malloc(strlen(src) + 1);
strcpy(dst,src);
strcat 把s2看看拷贝到s1的后面,结成一个长的字符串,s1必须有足够的空间
char* strcat(char* restrict s1,const char* restrict s2);
strcpy,strcat有可能因为空间不够造成问题,不够安全
安全版本
char *strncpy(char *restrict dst, const char *restrictsrc, size_t n);
char *strncat(char *restrict s1, const char *restricts2, size_t n);
int strncmp(const char *s1, const char *s2, size t n); //这里的n表示看前面多少位字符
字符串中找字符
char*strchr(const char *s, int c); //从左向右找
char * strrchr(const char *s, int c); //从右向左找
字符串中找字符串char *strstr(const char *s1, const char *s2);
char*strcasestr(const char *s1, const char *s2); //忽略大小写寻找
十一.结构类型
枚举
枚举是一种用户定义的数据类型,用enum以如下语法来声明:
enum 枚举数据名字{名字1,名字2,...,名字n};
枚举类型名字通常并不真的使用,要用的是在大括号里的名字因为它们就是就是常量符号,它们的类型是int,值则依次从0到n。
枚举只是int,声明枚举时可以指定值,如enum day{MON=1,TUE,FRI=5};
即使给枚举类型赋不存在的整数值也没有warning或error
枚举类型可以跟上enum作为类型,实际上,枚举是以整数来做内部计算和外部输出的
结构
声明结构类型
三种形式
struct point{
int x;
int y;
}; //记得要加分号
struct point p1,p2;
struct {
int x;
int y;
}p1,p2;
struct point{
int x;
int y;
}p1,p2;
在函数内部声明的结构只能在函数内部使用,所以通常在函数外部声明结构类型,这样就可以被多个函数使用了
结构用 . 运算符和名字访问其成员,如p1.x
结构运算
- 要访问整个结构,直接用结构变量的名字
- 对于整个结构,可以做赋值,取地址,也可以传递给函数参数
p1=(struct point){0,1); //(struct point)不能去掉
结构指针:和数组不同,结构变量的名字并不是结构变量的地址,必须用 &运算符
int a(struct point p)
- 整个结构可以作为参数的值传入函数,这时候是在函数内新建一个结构变量,并复制调用者结构的值(在函数里对新的结构变量作运算不会对调用者结构的值造成影响)
- 也可以返回一个结构
结构中的结构
struct point{
int x;;
int y;
};
struct a{{
struct point p1;
struct point p2;
};
如果有变量定义struct a r,*rp;rp=&r
那么下面四种形式是等价的
r.p1.x
rp->p1.x
(r.p1).x
(rp->p1).x
联合
自定义数据类型(typedef) 如typedef int INT;
使得INT成为int的别名
常用的用法:
typedef struct node {
int value;
struct node *next;
} Node;
//(把 struct node 叫做 Node)
将数组类型指定为一个新名字的用法:
typedef int Array[10];
//(Array 为长度为10的 int 数组)
字符串变量其实可以看作是char*
typedef char* Strings[10
代表 Strings 是长度为10的 char* 数组,
那么Strings 是10个字符串的数组
联合(union)
union ANELT{
int i;
char r;
}
- 储存
- 所有的成员共享一个空间
- 同一时间只有一个成员是有效的
- union的大小是其最大的成员
- 初始化
- 对第一个成员作初始化
union的用处:
#include <stdio.h>
typedef union {
int i;
char ch[sizeof(int)];
}CHI;
int main(int argc, char const *argv[]){
CHI chi;
int i;
chi.i = 1234;
for(i=0;i<sizeof(int);i++ ){
printf("%02hhx", chi.ch[i]);
}
printf("\n ",);
return 0;
}
x86,小端,低位在前
十二.程序结构
全局变量
- 定义在函数外面的变量是全局变量
- 全局变量具有全局的生存期和作用域
- 全局变量初始化时不建议将全局变量的值与另一个全局变量联系在一起
全局变量初始化
- 没有初始化的全局变量会得到0值,指针会得到NULL值
- 只能用编译时刻已知的值来初始化全局变量
- 如果函数内部存在与全局变量同名的变量,那么全局变量会被隐藏
静态本地变量
- 在本地变量定义时加上static修饰符就成为静态本地变量
- 当函数离开的时候,静态本地变量会继续存在并保持其值
- 静态本地变量的初始化只会在第一次进入这个函数时做,以后进入函数时会保持上次离开时的值
- 静态本地变量实际上是特殊的全局变量,它们位于相同的内存区域,具有全局的生存期,函数内的局部作用域
返回指针的函数
- 返回本地变量的地址是危险的,返回全局变量或静态本地变量的地址是安全的。
- 返回在函数内maloc的内存是安全的,但是容易造成问题,最好的做法是返回传入的指针。
- 使用全局变量和静态本地变量的函数是线程不安全的,不要使用全局变量来在函数间传递参数和结果,
尽量避免使用全局变量
编译预处理和宏
编译预处理指令#开头的是编译预处理指令
#define用来定义一个宏
- #define <名字><值> 注意结尾没有分号,名字必须是一个单词,值可以是各种东西
- 在C语言的编译器开始编译之前,编译预处理程序(cpp)会把程序中的名字换成值,完全的文本替换。
- 如果一个宏的值中有其他的宏的名字,也是会被替换的
- 如果一个宏的值超过一行,最后一行之前的行末需要加\
- 宏的值后面出现的注释不会被当作宏的值的一部分
没有值的宏:#define _DEBUG
这类宏是用于条件编译的,后面有其他的编译预处理指令来检查这个宏是否已经被定义过了
预定义的宏:
- __ LINE __
- __ FILE __
- __ DATE __
- __ TIME __
- __ STDC __
带参数的宏
#define cube(x)((x)*(x)*(x))
整个值要括号,参数出现的每个地方也都要括号
可以带多个参数,也可以组合(嵌套)使用其他宏
部分宏会被inline函数替代
宏的缺点,没有类型检查
大程序结构
一个.c文件是一个编译单元
项目
- 在Dev C++中新建一个项目,然后把几个源代码文件加入进去
- 对于项目,Dev C++的编译会把一个项目中所有的源代码文件都编译后,链接起来
- 把函数原型放到一个头文件(以.h结尾) 中,在需要调用这个函数的源代码文件(.c文件)中#include这个头文件,就能让编译器在编译的时候知道函数的原型
#include
- #include是一个编译预处理指令,和宏一样,在编译之前就处理了
- 它把那个文件的全部文本内容原封不动地插入到它所在的地方
- “”要求编译器首先在当前目录(.c文件所在的目录) 寻找这个文件,如果没有,到编译器指定的目录去找
- <>让编译器只在指定的目录去找
- 编译器自己知道自己的标准库的头文件在哪里,环境变量和编译器命令行参数也可以指定寻找头文件的目录
- #include <stdio.h>只是为了让编译器知道printf函数的原型,保证你调用时给出的参数值是正确的类型,现在的C语言编译器默认会引入所有的标准库
不对外公开的函数
- 在函数前面加上static就使得它成为只能在所在的编译单元中被使用的函数
- 在全局变量前面加上static就使得它成为只能在所在的编译单元中被使用的全局变量
声明
int i; //变量的定义
extern int i; //变量的声明
- 声明是不产生代码的东西,有函数原型;变量声明;结构声明;宏声明;枚举声明;类型声明;inline函数
- 定义是产生代码的东西,有函数,全局变量
头文件:只有声明可以被放在头文件中
同一编译单元中,同一结构不能被重复声明,所以需要“标准头文件结构”:
#ifndef __LIST_HESD__
#define __LIST_HESD__
#include "node.h"
typedef struct _list{
Node* hesd;
Node* tail;
}List
#endif
十三.文件
格式化的输入输出:
printf:%[flags][width][.prec][hlL]type
flag | 含义 |
---|---|
- | 左对齐 |
+ | 在前面放+或- |
(space) | 正数留空 |
0 | 0填充 |
width或prec | 含义 |
---|---|
number | |
* | 下一个参数是字符数 |
.number | 小数点后位数 |
*. | 下一个参数是小数点后位数 |
类型修饰 | 含义 |
---|---|
hh | 单个字节 |
h | short |
l | long |
ll | long long |
type | 用处 | type | 用处 |
---|---|---|---|
i或d | int | g或G | float |
u | unsigned int | a或A | 十六进制浮点 |
o | 八进制 | c | char |
x | 十六进制 | s | 字符串 |
X | 字母大写的十六进制 | p | 指针 |
f或F | float,6 | n | 读入或写出的个数 |
e或E | 指数 |
scanf:%[flag]type
flag | 含义 | flag | 含义 |
---|---|---|---|
* | 跳过 | l | long double |
数字 | 最大字符数 | ll | long long |
hh | char | L | long double |
h | short |
type | type | ||
---|---|---|---|
d | int | s | 字符串(单词) |
i | 可能为十六进制或八进制 | […] | 所允许的字符 |
u | unsigned int | p | 指针 |
o | 八进制 | x | 十六进制 |
a,e,f,g | float | c | char |
文件
文件输入输出,用<和>做重定向
输入结束:
- getchar读到了EOF
- scanf返回小于要求读的数量
- fscanf就是从文件读数据
- fprintf就是向文件写数据
FILE*fp = fopen("file","r");
if(fp){
fscanf(fp,...); //...里面和scanf一样
fclose(fp);
}else{
...
}
fopen()函数声明:
FILE *fopen(const char *filename,const char *mode)
const char *filename文件名,const char *mode模式
模式 | 作用 |
---|---|
“r” | 打开一个用于读取的文件。该文件必须存在。 |
“r+” | 打开一个用于更新的文件,可读取也可写入。该文件必须存在, |
“w” | 创建一个用于写入的空文件。如果文件名称与已存在的文件相同,则会删除已有文件的内容,文件被视为一个新的空文件。 |
“w+” | 创建一个用于读写的空文件。 |
“a” | 追加到一个文件。写操作向文件未尾追加数据。如果文件不存在,则创建文件。 |
“a+” | 打开一个用于读取和追加的文件。 |
二进制文件
- 所有的文件最终都是二进制的
- 文本文件是用最简单的方式可以读写的文件,
- 优点:方便人类读写;而且跨平台
- 缺点:输入输出是格式化,可能经过转码
- 二进制文件是需要专门的程序来读写的文件
- 优点:程序读写快
- 缺点:人类读写困难,而且不跨平台
//二进制读写
size t fread(void *restrict ptr, size tsize, size tnitems, FlLE *restrict stream);
size t fwrite(const void *restrict ptr, size t sizesize t nitems, FlLE *restrict stream);
//返回的是成功读写的字节数
long ftell(FlLE *stream); //返回文件指针相对于起始位置的偏移量,
int fseek(FlLE *stream, long offset, int whence);
/*SEEK SET:从头开始
SEEK CUR:从当前位置开始
SEEK END:从尾开始(倒过来)*/
可移植性(不是很懂(((φ(◎ロ◎;)φ)))
课里这样说
- 这样的二进制文件不具有可移植性
- 在int为32位的机器上写成的数据文件无法直接在int为64位的机器上正确读出
- 解决方案之一是放弃使用int,而是typedef具有明确大小的类型,更好的方案是用文本