C程序设计的补充(tips)

写在开头:
看完《C程序设计》这本书,真心不推荐这本书和用这本书入门学C语言,幸亏之前学了,不然在i++(++)这种乱七八糟的写法就晕了= =还有一些用法也比较鸡肋——或者说鸡肋不准确,它既没解释清楚,也没告诉人怎么用才好。C语言还是推荐跟着翁凯的课+《C language》(译版)+了解一些实用的C++写法更好~

————————————分割线————————————

最近在重头看谭浩强的《C程序设计》,可能是已经学了翁凯的C语言课程(指路B站、mooc),觉得这本书写的特别有调理!当然,也有一些我觉得没怎么见过的用法,比如连续赋值啊之类的= =
这篇算是查漏补缺,记录自己看到哪了、哪些需要tips(不完整梳理、只记录自己觉得之前不熟悉的内容),目标在3.6-3.10看完~

3.6更新————————————

1. 标志符
C 规定,标识符(变量名称、函数名称等)只能是字母(A~Z, a~z)、数字(0~9)、下划线(_)组成的字符串,并且其第一个字符必须是字母或下划线。

2. 不允许声明时连续赋初值
类似:

#include <stdio.h>
#include <algorithm>
using namespace std;

int main(){
    
    
	int a = b = c = d = 5;
	printf("%d %d %d %d",a,b,c,d);
}

不合法,但事先声明变量之后,就合法:

#include <stdio.h>
#include <algorithm>
using namespace std;

int main(){
    
    
	int a,b,c,d;
	a = b = c = d = 5;
	printf("%d %d %d %d",a,b,c,d);
}

3. 逗号运算符和逗号表达式
在C语言中逗号“,”也是一种运算符,称为逗号运算符。 其功能是把两个表达式连接起来组成一个表达式, 称为逗号表达式。
其一般形式为:
表达式 1,表达式 2
其求值过程是分别求两个表达式的值,并以表达式 2 的值作为整个逗号表达式的值。
【例 3.19】

main(){
    
    
	int a=2,b=4,c=6,x,y;
	y=(x=a+b),(b+c);
	printf("y=%d,x=%d",y,x);
}

本例中, y 等于整个逗号表达式的值,也就是表达式 2 的值, x 是第一个表达式的值。

4. printf格式字符串
[标志][输出最小宽度][.精度][长度]类型

  1. 类型:类型字符用以表示输出数据的类型
    2) 标志:标志字符为-、+、#、空格四种,其意义下表所示:
- 结果左对齐,右边填空格
+ 输出符号(正号或负号)
空格 输出值为正时冠以空格,为负时冠以负号
# 对 c,s,d,u 类无影响;对 o 类,在输出时加前缀 o;对 x 类,在输出时加前缀 0x;对 e,g,f 类当结果有小数时才给出小数点
  1. 输出最小宽度:用十进制整数来表示输出的最少位数。若实际位数多于定义的宽度,
    则按实际位数输出,若实际位数少于定义的宽度则补以空格或 0。
  2. 精度:精度格式符以“.”开头,后跟十进制整数。本项的意义是:如果输出数字,
    则表示小数的位数;如果输出的是字符,则表示输出字符的个数;若实际位数大于
    所定义的精度数,则截去超过的部分。
    5.长度:长度格式符为 h,l 两种,h 表示按短整型量输出,l 表示按长整型量输出。

5.scanf格式字符串

%[*][输入数据宽度][长度]类型

“*”符:用以表示该输入项,读入后不赋予相应的变量,即跳过该输入值。
如:
scanf("%d %*d %d",&a,&b);
当输入为:1 2 3 时,把 1 赋予 a,2 被跳过,3 赋予 b。

宽度:用十进制整数指定输入的宽度(即字符数)。
例如:
scanf("%5d",&a);
输入:12345678
只把 12345 赋予变量 a,其余部分被截去。

3.7更新————————————

6. if else
C语言规定,else 总是与它前面最近的 if 配对

7. 不能在方括号中用变量来表示元素的个数, 但是可以是符号常数或常量表达式。
在最新的C编译下,是可用的(C99可以);比如以下是合法的:

#include <stdio.h>

int main(){
    
    
	int n = 5;
	int A[n];
	for(int i = 0;i <n;i++){
    
    
		A[i] = i+1;
	}
	for(int i = 0;i <n;i++){
    
    
		printf("%d ",A[i]);
	}
}

3.8更新————————————

8. auto 变量
在调用该函数时系统会给它们分配存储空间,在函数调用结束时就自动释
放这些存储空间。这类局部变量称为自动变量。自动变量用关键字 auto 作存储类别的声明。
例如:

int f(int a) /*定义 f 函数, a 为参数*/
	{
    
    auto int b,c=3; /*定义 b, c 自动变量*/
}

9. static 声明局部变量
有时希望函数中的局部变量的值在函数调用结束后不消失而保留原值,这时就应该指定局部变量为“静态局部变量”,用关键字 static 进行声明。
例如:

f(int a)
{
    
    
	auto b=0;
	static c=3;
	b=b+1;
	c=c+1;
	return(a+b+c);
}

对静态局部变量的说明:

  1. 静态局部变量属于静态存储类别,在静态存储区内分配存储单元。在程序整个运行期间都不释放。而自动变量(即动态局部变量)属于动态存储类别,占动态存储空间,函数调用结束后即释放。
  2. 静态局部变量在编译时赋初值,即只赋初值一次;而对自动变量赋初值是在函数调用时进行,每调用一次函数重新给一次初值,相当于执行一次赋值语句。
  3. 如果在定义局部变量时不赋初值的话,则对静态局部变量来说,编译时自动赋初值 0(对数值型变量)或空字符(对字符变量)。而对自动变量来说,如果不赋初值则它的值是一个不确定的值。

10. register 变量
详见百度,感觉意义不大

11. 用 extern 声明外部变量
extern是在定义外部变量位于文件尾,或在使用处的后面,则用extern定义(感觉意义不大)

12. 条件编译
要注意的是宏定义是以实参替换形参,而不是值传入
预处理程序提供了条件编译的功能。可以按不同的条件去编译不同的程序部分,因而产生不同的目标代码文件。这对于程序的移植和调试是很有用的。
条件编译有三种形式,下面分别介绍:
1>第一种形式:
#ifdef 标识符
程序段 1
#else
程序段 2
#endif
它的功能是,如果标识符已被 #define 命令定义过则对程序段 1 进行编译;否则对程序段 2 进行编译。如果没有程序段 2(它为空),本格式中的#else 可以没有,即可以写为:
#ifdef 标识符
程序段
#endif

2>第二种形式
#ifndef 标识符
程序段 1
#else
程序段 2
#endif
与第一种形式的区别是将“ifdef”改为“ifndef”。它的功能是,如果标识符未被
#define 命令定义过则对程序段 1 进行编译,否则对程序段 2 进行编译。这与第一种形式的功能正相反。

3>第三种形式
#if 常量表达式
程序段 1
#else
程序段 2
#endif
它的功能是,如常量表达式的值为真(非 0),则对程序段 1 进行编译,否则对程序段 2进行编译。因此可以使程序在不同条件下,完成不同的功能。

13. 指针运算符和指针变量说明中的指针说明符不是一回事。
需要注意的是指针运算符和指针变量说明中的指针说明符不是一回事。在指针变量说明中, “”是类型说明符,表示其后的变量是指针类型。而表达式中出现的“”则是一个运算符用以表示指针变量所指的变量。
即下面两处*意义不同:

int *p;
p = &a;
printf("%d\n",*p);

3.9更新————————————

14. 指向多维数组的指针变量
把二维数组 a 分解为一维数组 a[0],a[1],a[2]之后,设 p 为指向二维数组的指针变量。
可定义为:
int (*p)[4]
它表示 p 是一个指针变量,它指向包含 4 个元素的一维数组。二维数组指针变量说明的一般形式为:
类型说明符 (*指针变量名)[长度]
例如:

main(){
    
    
	int a[3][4]={
    
    0,1,2,3,4,5,6,7,8,9,10,11};
	int(*p)[4];
	int i,j;
	p=a;
	for(i=0;i<3;i++)
	{
    
    for(j=0;j<4;j++) printf("%2d ",*(*(p+i)+j));
	printf("\n");
	}
}

15. 函数指针变量:指向函数的指针
这种用法没用过,但如果涉及函数中的修改,用&感觉更方便
函数指针变量定义的一般形式为:
类型说明符 (*指针变量名)()
其中“类型说明符”表示被指函数的返回值的类型。“(* 指针变量名)”表示“*”后面的变量是定义的指针变量。最后的空括号表示指针变量所指的是一个函数。
这一段对着书上代码敲,发现不通过,查了资料之后发现改成如下形式就通过了:

#include <stdio.h>

int maxx(int a,int b){
    
    
	if(a>b){
    
    
		return a;
	}
	else{
    
    
		return b;
	}
}

int main(){
    
    
	int (*pmax)(int,int);  //这里要写出变量的类型、个数 
	pmax = maxx;
	int x,y,z;
	printf("input two numbers:\n");
	scanf("%d %d",&x,&y);
	z = (*pmax)(x,y);
	printf("%d",z);
}

16. 指针型函数:函数返回的就是指针
代码(有warning未解决):

#include <stdio.h>

char *day_name(int n){
    
    
	static char *name[] = {
    
    
		"Illegal day",
		"Monday",
		"Tuesday",
		"Wednesday",
		"Thursday",
		"Friday",
		"Saturday",
		"Sunday"
	};
	return (n <1 || n >7) ? name[0] :name[n];
}

int main(){
    
    
	int i;
	char *day_name(int n);
	printf("input Day No:\n");
	scanf("%d",&i);
	if(i<0){
    
    
		printf("WRONG!");
	}
	else{
    
    
		printf("Day No:%2d-->%s\n",i,day_name(i));
	}
	
}

17. 指针数组:存放相同类型指针的数组
可能是我写的代码太少了,感觉指针这好几个功能蛮鸡肋的= =

18. main 函数的参数
在main中传入参数,感觉蛮鸡肋的= =(好吧我承认是太懒了,感觉用不到,不想了解。。。)

19. 有关指针的数据类型的小结
这个蛮实用的~
有关指针的数据类型的小结

19. 动态存储分配

  1. 分配内存空间函数 malloc
    调用形式:
    (类型说明符*)malloc(size)
    功能:在内存的动态存储区中分配一块长度为"size"字节的连续区域。函数的返回值为该区域的首地址。
    “类型说明符”表示把该区域用于何种数据类型。
    (类型说明符*)表示把返回值强制转换为该类型指针。
  2. 分配内存空间函数 calloc
    calloc 也用于分配内存空间。
    调用形式:
    (类型说明符*)calloc(n,size)
    功能:在内存动态存储区中分配 n 块长度为“size”字节的连续区域。函数的返回值为该区域的首地址。
    (类型说明符*)用于强制类型转换。
    calloc 函数与 malloc 函数的区别仅在于一次可以分配 n 块区域。
  3. 释放内存空间函数 free
    调用形式:
    free(void*ptr);

2020.3.10更新————————————完结撒花~
20. 位运算

“&” 按位与
按位与运算符"&"是双目运算符。 其功能是参与运算的两数各对应的二进位相与。只有对应的两个二进位均为 1 时, 结果位才为 1, 否则为 0。 参与运算的数以补码方式出现。
例如:9&5 可写算式如下:
00001001 (9 的二进制补码)
&00000101 (5 的二进制补码)
00000001 (1 的二进制补码)
可见 9&5=1。
按位与运算通常用来对某些位清 0 或保留某些位。例如把 a 的高八位清 0 ,保留低八位,可作 a&255 运算( 255 的二进制数为 0000000011111111)。

#include <stdio.h>
int main(){
    
    
	int a=9,b=5,c;
	c=a&b;
	printf("a=%d\nb=%d\nc=%d\n",a,b,c);
}

| 按位或
按位或运算符“|”是双目运算符。其功能是参与运算的两数各对应的二进位相或。只要对应的二个二进位有一个为 1 时,结果位就为 1。参与运算的两个数均以补码出现。
例如:9|5 可写算式如下:
00001001
|00000101
00001101 (十进制为 13)可见 9|5=13

#include <stdio.h>

int main(){
    
    
	int a=9,b=5,c;
	c=a|b;
	printf("a=%d\nb=%d\nc=%d\n",a,b,c);
}

“^” 按位异或
按位异或运算符“^”是双目运算符。其功能是参与运算的两数各对应的二进位相异或,当两对应的二进位相异时,结果为 1。参与运算数仍以补码出现,例如 9^5 可写成算式
如下:
00001001
^00000101
00001100 (十进制为 12)

#include <stdio.h>

int main(){
    
    
	int a=9;
	a=a^5;
	printf("a=%d\n",a);
}

“~” 取反
求反运算符~为单目运算符,具有右结合性。其功能是对参与运算的数的各二进位按位求反。
例如~9 的运算为:
~(0000000000001001)结果为:1111111111110110

“<<” 左移
左移运算符“<<”是双目运算符。其功能把“<< ”左边的运算数的各二进位全部左移若干位,由“<<”右边的数指定移动的位数,高位丢弃,低位补 0。
例如:
a<<4
指把 a 的各二进位向左移动 4 位。如 a=00000011(十进制 3),左移 4 位后为 00110000(十进制 48)。

“>>” 右移
右移运算符“>>”是双目运算符。其功能是把“>> ”左边的运算数的各二进位全部右移若干位, “>>”右边的数指定移动的位数。
例如:
设 a=15,
a>>2
表示把 000001111 右移为 00000011(十进制 3)。
应该说明的是,对于有符号数,在右移时,符号位将随同移动。当为正数时,最高位补0,而为负数时,符号位为 1,最高位是补 0 或是补 1 取决于编译系统的规定。

#include <stdio.h>

int main(){
    
    
	unsigned a,b;
	printf("input a number: ");
	scanf("%d",&a);
	b=a>>5;
	b=b&15;
	printf("a=%d\tb=%d\n",a,b);
}

21.位域(位段)
这部分详细使用还是看百度,这里只放一个案例:

#include <stdio.h>

int main(){
    
    
	struct bs
	{
    
    
	unsigned a:1;
	unsigned b:3;
	unsigned c:4;
	} bit,*pbit;
	bit.a=1;
	bit.b=7;
	bit.c=15;
	printf("%d,%d,%d\n",bit.a,bit.b,bit.c);
	pbit=&bit;
	pbit->a=0;
	pbit->b&=3;
	pbit->c|=1;
	printf("%d,%d,%d\n",pbit->a,pbit->b,pbit->c);
}

22. 文件操作
比较实用,之前也没用过,记录下~
文件的打开(fopen 函数)
例如:
FILE *fp;
fp=(“file a”,“r”);
其意义是在当前目录下打开文件 file a,只允许进行“读”操作,并使 fp 指向该文件。
又如:
FILE *fphzk
fphzk=(“c:\hzk16”,“rb”)
其意义是打开 C 驱动器磁盘的根目录下的文件 hzk16,这是一个二进制文件,只允许按二进制方式进行读操作。两个反斜线“\ ”中的第一个表示转义字符,第二个表示根目录。

对于文件使用方式有以下几点说明:

  1. 文件使用方式由 r,w,a,t,b,+六个字符拼成,各字符的含义是:
    r(read): 读
    w(write): 写
    a(append): 追加
    t(text): 文本文件,可省略不写
    b(banary): 二进制文件
    +: 读和写
  2. 凡用“r”打开一个文件时,该文件必须已经存在,且只能从该文件读出。
  3. 用“w”打开的文件只能向该文件写入。若打开的文件不存在,则以指定的文件名建立该文件,若打开的文件已经存在,则将该文件删去,重建一个新文件。
  4. 若要向一个已存在的文件追加新的信息,只能用“a”方式打开文件。但此时该文件必须是存在的,否则将会出错。
  5. 在打开一个文件时,如果出错,fopen 将返回一个空指针值 NULL。在程序中可以用这一信息来判别是否完成打开文件的工作,并作相应的处理。因此常用以下程序段打开文件:
    if((fp=fopen(“c:\hzk16”,“rb”)==NULL)
    {
    printf("\nerror on open c:\hzk16 file!");getch();
    exit(1);
    }

这段程序的意义是,如果返回的指针为空,表示不能打开 C 盘根目录下的 hzk16 文件,则给出提示信息“error on open c:\ hzk16 file!”,下一行 getch()的功能是从键盘输入一个字符,但不在屏幕上显示。在这里,该行的作用是等待,只有当用户从键盘敲任一键时,程序才继续执行,因此用户可利用这个等待时间阅读出错提示。敲键后执行exit(1)退出程序

使用文件的方式共有 12 种,下面给出了它们的符号和意义。
在这里插入图片描述
文件关闭函数(fclose 函数)
fclose 函数调用的一般形式是:
fclose(文件指针);
例如:
fclose(fp);

文件的读写
字符读写函数 fgetc 和 fputc
fgetc 函数的功能是从指定的文件中读一个字符,函数调用的形式为:
字符变量=fgetc(文件指针);
例如:
ch=fgetc(fp);
其意义是从打开的文件 fp 中读取一个字符并送入 ch 中。

#include <stdio.h>
#include <conio.h>
#include <stdlib.h>

int main()
{
    
    
FILE *fp;
char ch;
if((fp=fopen("d:\\jrzh\\example\\c1.txt","rt"))==NULL){
    
    
	printf("\nCannot open file strike any key exit!");
	getch();   //调用<conio.h>请求头 
	exit(1);   //调用<stdlib.h>请求头 
}

ch=fgetc(fp);
while(ch!=EOF)
{
    
    
putchar(ch);
ch=fgetc(fp);
}
fclose(fp);
}

fputc 函数的功能是把一个字符写入指定的文件中,函数调用的形式为:
fputc(字符量,文件指针);
其中,待写入的字符量可以是字符常量或变量,例如:
fputc(‘a’,fp);
其意义是把字符 a 写入 fp 所指向的文件中。

#include <stdio.h>
#include <conio.h>
#include <stdlib.h>

int main()
{
    
    
	FILE *fp;
	char ch;
	if((fp=fopen("d:\\jrzh\\example\\string","wt+"))==NULL)
	{
    
    
	printf("Cannot open file strike any key exit!");
	getch();
	exit(1);
	}
	printf("input a string:\n");
	ch=getchar();
	while (ch!='\n')
	{
    
    
	fputc(ch,fp);
	ch=getchar();
	}
	rewind(fp);
	ch=fgetc(fp);
	while(ch!=EOF)
	{
    
    
	putchar(ch);
	ch=fgetc(fp);
	}
	printf("\n");
	fclose(fp);

}

字符串读写函数 fgets 和 fputs
读字符串函数 fgets
函数的功能是从指定的文件中读一个字符串到字符数组中,函数调用的形式为:
fgets(字符数组名,n,文件指针);
其中的 n 是一个正整数。表示从文件中读出的字符串不超过 n-1 个字符。在读入的最后一个字符后加上串结束标志’\0’。
例如:
fgets(str,n,fp);
的意义是从 fp 所指的文件中读出 n-1 个字符送入字符数组 str 中。

写字符串函数 fputs
fputs 函数的功能是向指定的文件写入一个字符串,其调用形式为:
fputs(字符串,文件指针);
其中字符串可以是字符串常量,也可以是字符数组名,或指针变量,例如:
fputs(“abcd“,fp);
其意义是把字符串“abcd”写入 fp 所指的文件之中。

数据块读写函数 fread 和 fwtrite
C语言还提供了用于整块数据的读写函数。可用来读写一组数据,如一个数组元素,一个结构变量的值等。
读数据块函数调用的一般形式为:
fread(buffer,size,count,fp);
写数据块函数调用的一般形式为:
fwrite(buffer,size,count,fp);
其中:
buffer 是一个指针,在 fread 函数中,它表示存放输入数据的首地址。在 fwrite 函数中,它表示存放输出数据的首地址。
size 表示数据块的字节数。
count 表示要读写的数据块块数。fp 表示文件指针。

例:从键盘输入两个学生数据,写入一个文件中,再读出这两个学生的数据显示在屏幕上。(数字输出乱码,讲真这书里面的代码挺坑的= =)

#include<stdio.h>
#include <conio.h>
#include <stdlib.h>
#include <iostream>
using namespace std;

struct stu
{
    
    
char name[10];
int num;
int age;
char addr[15];
}boya[2],boyb[2],*pp,*qq;

int main()
{
    
    
	FILE *fp;
	char ch;
	int i;
	pp=boya;
	qq=boyb;
	if((fp=fopen("C:\\Users\\ASUS\\Desktop\\C练习文件\\record.txt","wb+"))==NULL)
	{
    
    
	printf("Cannot open file strike any key exit!");
	getch();
	exit(1);
	}
	printf("\ninput data\n");
	for(i=0;i<2;i++,pp++){
    
    
		cin>>pp->name>>pp->num>>pp->age>>pp->addr;   //依然会有数字乱码的问题 
		//scanf("%s %d %d %s",pp->name,&pp->num,&pp->age,pp->addr);
	}
	
	pp=boya;
	fwrite(pp,sizeof(struct stu),2,fp);
	rewind(fp);
	fread(qq,sizeof(struct stu),2,fp);
	printf("\n\nname\tnumber age addr\n");
	for(i=0;i<2;i++,qq++)
	printf("%s\t%5d%7d %s\n",qq->name,qq->num,qq->age,qq->addr);
	fclose(fp);
}

/*
eve 100001 25 aaa
bobo 100002 26 bbb
*/

格式化读写函数 fscanf 和 fprintf
fscanf 函数,fprintf 函数与前面使用的 scanf 和 printf 函数的功能相似,都是格式化读写函数。 两者的区别在于 fscanf 函数和 fprintf 函数的读写对象不是键盘和显示器, 而是磁盘文件。
这两个函数的调用格式为:
fscanf(文件指针,格式字符串,输入表列);
fprintf(文件指针,格式字符串,输出表列);
例如:
fscanf(fp,"%d%s",&i,s);
fprintf(fp,"%d%c",j,ch);

例:(同上)(强烈建议此种写法,没有乱码的问题)

#include<stdio.h>

using namespace std;

struct stu
{
    
    
char name[10];
int num;
int age;
char addr[15];
}boya[2],boyb[2],*pp,*qq;

int main()
{
    
    
	FILE *fp;
	char ch;
	int i;
	pp=boya;
	qq=boyb;
	if((fp=fopen("record.txt","wb+"))==NULL)
	{
    
    
	printf("Cannot open file strike any key exit!");
	getchar();
	
	}
	printf("\ninput data\n");
	for(i=0;i<2;i++,pp++){
    
    
		scanf("%s%d%d%s",pp->name,&pp->num,&pp->age,pp->addr);
	}
	pp=boya;
	
	for(i=0;i<2;i++,pp++){
    
    
		fprintf(fp,"%s %d %d %s\n",pp->name,pp->num,pp->age,pp->addr);
	}
	
	rewind(fp);
	for(i=0;i<2;i++,qq++){
    
    
		fscanf(fp,"%s %d %d %s\n",qq->name,&qq->num,&qq->age,qq->addr);
	}
	printf("\n\nname\tnumber age addr\n");
	qq=boyb;
	for(i=0;i<2;i++,qq++){
    
    
		printf("%s\t%5d %7d %s\n",qq->name,qq->num, qq->age,qq->addr);
	}
	
	fclose(fp);
}

/*
eve 100001 25 aaa
bobo 100002 26 bbb
*/

文件定位
移动文件内部位置指针的函数主要有两个,即 rewind 函数和 fseek 函数。
rewind 函数前面已多次使用过,其调用形式为:
rewind(文件指针);
它的功能是把文件内部的位置指针移到文件首。

下面主要介绍 fseek 函数。
fseek 函数用来移动文件内部位置指针,其调用形式为:
fseek(文件指针,位移量,起始点);
其中:
“文件指针”指向被移动的文件。
“位移量”表示移动的字节数,要求位移量是 long 型数据,以便在文件长度大于 64KB 时不会出错。当用常量表示位移量时,要求加后缀“L”。
“起始点”表示从何处开始计算位移量,规定的起始点有三种:文件首,当前位置和文件尾。
其表示方法如下表。
在这里插入图片描述

文件检测函数
文件结束检测函数 feof 函数
调用格式:
feof(文件指针);
功能:判断文件是否处于文件结束位置,如文件结束,则返回值为 1,否则为 0。

读写文件出错检测函数
ferror 函数调用格式:
ferror(文件指针);
功能:检查文件在用各种输入输出函数进行读写时是否出错。 如 ferror 返回值为 0 表示未出错,否则表示有错。

文件出错标志和文件结束标志置 0 函数
clearerr 函数调用格式:
clearerr(文件指针);
功能:本函数用于清除出错标志和文件结束标志,使它们为 0 值。

暂时就写这么多后序有更新再补上

猜你喜欢

转载自blog.csdn.net/weixin_42377217/article/details/104700395