复合类型
枚举
enum
{
MON=1,TUE,WED,SUN
}day;
结构体
定义与初始化
struct stu{
char a[20];
long long number;
int gender;
};
main(){
struct stu Mike,Bob;
}
//
struct stu{
char a[20];
long long number;
int gender;
}Mike={"Mike",82020130241,1};
结构体数组
struct stu stu[100];
结构体成员的使用
Mike.number //普通变量
struct *p=&Mike;
(*p).number; //way1
p->numer; //way2 ->优先级别最高 //指针形式
结构体数组的指针类似
堆区struct变量
struct stu *p = NULL;
p=(struct stu *)malloc(sizeof(stuct stu));
强制转换
struct complex{
double Re;
double Im;
};
struct complex addmor;
addmor=(struct complex){3.2,5.6};
共用体(联合体)
用途:如,把一个东西按二进制写到文件里,union可能是一个媒介
union
是一个能在同一个存储空间存储不同类型数据的类型
联合体所占的内存长度等于其最长成员的长度倍数,同一内存段可以用来存放几种不同类型的成员,但每一瞬时只有一种起作用
共用体变量中起作用的成员是最后一次存放的成员,在存入一个新的成员后原有的成员的值会被覆盖
//左边是高位,右边是低位
//低位放低地址,高位放高地址
#include <stdio.h>
union CHI
{
int i;
char ch[sizeof(int)];
};
int main(int argc, char const *argv[])
{
union CHI chi;
int i;
chi.i = 1234;
for ( i = 0; i < sizeof(int); i++)
{
printf("%02hhX",chi.ch[i]);
}
return 0;
}//大小端
typedef
为一种数据定义一个新名字
typedef int ZHENGSHU;
链表
简单应用
#include "node.h"
#include <stdio.h>
typedef struct _node
{
long number;
char name[20];
float score;
struct _node *next;
} Node;
int main(int argc, char const *argv[])
{
Node s1 = {2020130235, "Liu Han", 98.45};
Node s2 = {2020130233, "LAn Han", 32.28};
Node s3 = {2020130234, "fan Han", 62.28};
Node *head, *p;
head = &s1;
s1.next = &s2;
s2.next = &s3;
s3.next = NULL;
p = head;
while (p!=NULL)
{
printf("%ld %s %.2f",p->number,p->name,p->score);
p=p->next; //指向下一个节点
}
return 0;
}
链表的建立
采用尾插法建立单向链表的过程有如下几步。
(1)建立一个空链表。
(2)利用malloc() 函数分配一个新结点, 给新结点的成员赋值
(3)将新结点加入链表尾部。
(4)判断是否有后续结点,若无则建立链表结束,若有则转到(2)。
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
typedef struct _node
{
long number;
char name[20];
float score;
struct _node *next;
} node;
node *createNode();
int main()
{
node *head;
head = createNode(); //方法一
node *p = head;
while (p != NULL)
{
printf("%ld %s %.2f\n", p->number, p->name, p->score);
p = p->next; //指向下一个节点
} //输出链表
return 0;
}
//方法一
node *createNode()
{
node *head;
node *p1, *p2;
long number;
char name[20];
float score;
head = NULL;
scanf("%ld %s %f", &number, name, &score);
while (number != 0)
{
p1 = (node *)malloc(sizeof(node)); //为新节点分配内存空间
p1->number = number;
strcpy(p1->name, name);
p1->score = score;
p1->next = NULL;
if (head == NULL)
head = p1;
else
p2->next = p1;
p2 = p1;
scanf("%ld %s %f", &number, name, &score);
}
//逻辑有点绕,说明:首先为p1所指创建一个node的空间,然后赋值,第一个空间被head所指
//然后,把p1的地址给了p2,p1开辟一块新的空间,然后填内容,然后执行p2->next = p1,意为原本p1->next 指向了新空间的p1,如此往复
return head;
}
//方法二 翁恺
node *createNodeWayTwo()
{
node *head = NULL;
node *p, *last;
long number;
char name[20];
float score;
do
{
scanf("%ld %s %f", &number, name, &score);
if (number)
{
p = (node *)malloc(sizeof(node));
p->number = number;
strcpy(p->name, name);
p->score = score;
p->next = NULL;
// find the last
last = head;
if (head!=NULL)
{
while (last->next)
{
last = last->next;
}
// attach
last->next = p;
}
else
head = p;
}
} while (number);
}
删除节点
第一个节点:用指针保存第一个节点的地址,把head指向下一个节点,使之成为第一个节点,最后释放第一个节点的空间
最后的节点和中间节点:同理,保存地址,前一个节点指向后一个节点(或NULL),再释放空间
#include "node.h"
#include <stdlib.h>
#include <stdio.h>
typedef struct list
{
Node *head; //始终指向第一个结点的指针
Node *tail; //始终指向最后一个结点的指针
} List;
void add(List *plist, int number); //添加数值到结点
void print(List *plist); //输出链表中的值
void found(List *fList); //中查找指定数值
void deletee(List *fList); //删除指定数值
void deleteall(Node **phead);
int main(void)
{
printf("Please enter the data you need to store(-1 is to end):");
//链表开始为空
List list;
list.head = NULL;
list.tail = NULL;
int number;
do
{
scanf("%d", &number);
if (number != -1)
{
add(&list, number);
}
} while (number != -1);
print(&list);
found(&list);
deletee(&list);
return 0;
}
void add(List *plist, int number)
{
// add to linked-list
Node *p = (Node *)(malloc(sizeof(Node))); //每读入一个数字就得制造一个结点来存储它
p->value = number; //将读入的数值存储在value区
p->next = NULL; //将next区置零(空指针):链表的最后一个结点的next区必须是空指针
// find the last number
Node *last = plist->tail; // tail结点总是要指向最后一个结点(last结点)(在main函数开始时List的结点成员全为NULL)
if (last)
{
// while (last->next){
// last = last->next;
last->next = p; //如果last存储了数值,那么将last的next就指向p,就是将p结点链接到last结点后面
plist->tail = p; //此时tail结点就是(指向)新结点p
}
else
{
//如果last没有存储数值,那么此时只有一个结点p,那么链表的首尾结点肯定都是它了
plist->head = p;
plist->tail = p;
}
}
void print(List *pList)
{
Node *p;
for (p = pList->head; p; p = p->next)
{ //从头开始查找数值,只要p里面有数值,就一直找下一个结点
printf("%d\t", p->value);
}
printf("\n");
}
void found(List *fList)
{
int number;
printf("What value do you want to find ?");
scanf("%d", &number);
Node *p;
int isFound = 0;
for (p = fList->head; p; p = p->next)
{
if (p->value == number)
{
printf("Found it!\n");
isFound = 1;
break;
}
}
if (!isFound)
{
printf("Not found it!\n");
}
}
void deletee(List *fList)
{
int number;
printf("What value do you want to delete ?");
scanf("%d", &number);
Node *p, *q;
//如果要删除的结点p是第一个结点,那么q就指向了空指针(因为中间没有结点可绕)
for (q = NULL, p = fList->head; p; q = p, p = p->next)
{
if (p->value == number)
{
if (q)
{ //如果q是非空指针
q->next = p->next; //删除结点原理:要删除结点p的值.只需要让q的next指向p的next指向的结点(就是绕过结点p)
}
else
{
fList->head = p->next; //否则就让p的next指向头指针(它自己)
}
free(p); //绕过的结点p必须释放其内存(因为是malloc申请的)
printf("It's been deleted !\n");
break;
}
}
//删除整个链表:因为链表malloc申请的内存块,用完必须释放
for (p = fList->head; p; p = q)
{ //让p指向首结点(从头开始删除),删除p之后将q复制给p:就是让p和q每次往下一个结点移动
q = p->next; //让q指向p的下一个结点
free(p); //然后就可以删除p了
}
printf("For safety, The entire list is deleted after the program ends !");
}
void deleteall(Node **phead)
{
Node *p, *q;
for (p = *phead; p; p = q)
{
q = p->next;
free(p);
}
}
void deletesome(Node **phead, int number)
{
Node *p, *q; // q is p->next
for (p = *phead, q = NULL; p; q = p, p = p->next)
{
if (p->value == number)
{
if (q == NULL)
{
*phead = p->next;
}
else
{
q->next = p->next; //绕过节点p
}
free(p);
}
}
}
void delete_the_nth_node(Node **phead, int number)
{
Node *p = (*phead), *q = (*phead);
if (number == 1)
{
*phead = (*phead)->next;
free(p);
}
else
{
for (int i = 1; i < number; i++)
{
q = p;
p = p->next;
}
q->next = p->next;
free(p);
}
}
文件
声明 FILE 结构体类型的信息包含在头文件“stdio.h”中
FILE *fp_passwd = fopen("passwd.txt", "r");当前目录
c:/test/password.txt 绝对目录
../pass.txt 上一级目录
./test/pass.txt 当前目录的test目录
if (fp == NULL) //返回空,说明打开失败
{
//perror()是标准出错打印函数,能打印调用库函数出错原因
perror("open");
return -1;
}
int stat(const char *path, struct stat *buf);
获取文件状态信息
path:文件名
buf:保存文件信息的结构体
成功:0
失败-1
判断是否到文件结尾:if(feof(fp)==0)
或while(!feof(fp))
代表没有到文件结尾
fgetc fputc 按照字符读写
fgets fputs 按照行读写文件
char buf[1000];
while (!feof(fp)) //文件没有结束
{
memset(buf, 0, sizeof(buf));
char *p = fgets(buf, sizeof(buf), fp);
if (p != NULL) {
printf("buf = %s", buf);
} }
fprintf fscanf 按格式读写
fread fwrite 按块读写
四则运算的例子
#include <stdio.h>
#include<string.h>
struct comput
{
int firstnumber;
int secondnumber;
char chara;
};
int main(){
struct comput comput;
FILE *fp;
FILE *FILE_NEW;
double value;
fp=fopen("1.txt","r");
FILE_NEW=fopen("2.txt","w");
char buf[20];
while(!feof(fp)){
memset(buf, 0, 20);
fgets(buf,100,fp);
sscanf(buf,"%d%c%d=\n",&comput.firstnumber,&c
omput.chara,&comput.secondnumber);
switch (comput.chara)
{
case '+':
value=comput.firstnumber+comput.secondnumber;
break;
case '-':
value=comput.firstnumber-comput.secondnumber;
break;
case '*':
value=comput.firstnumber*comput.secondnumber;
break;
case '/':
value=comput.firstnumber*1.0/comput.secondnumber;
break;
default:
break;
}
fprintf(FILE_NEW,"%d%c%d=%.2lf\n",comput.firstnumber,comput.chara,comput.secondnumber,value);
}
fclose(fp);
fclose(FILE_NEW);
return 0;
}
文件的随机读写
int fseek(FILE *stream, long offset, int whence);
移动文件流(文件光标)的读写位置。
stream:已经打开的文件指针
offset:根据whence来移动的位移数(偏移量),可以是正数,也可以
负数,如果正数,则相对于whence往右移动,如果是负数,则相对于wh
ence往左移动。如果向前移动的字节数超过了文件开头则出错返回,如果
向后移动的字节数超过了文件末尾,再次写入时将增大文件尺寸。
whence:其取值如下:
SEEK_SET:从文件开头移动offset个字节
SEEK_CUR:从当前位置移动offset个字节
SEEK_END:从文件末尾移动offset个字节
void rewind(FILE *stream);
把文件流(文件光标)的读写位置移动到文件开头。
stream:已经打开的文件指针
无返回值
long ftell(FILE *stream);
获取文件流(文件光标)的读写位置。
stream:已经打开的文件指针
成功:当前文件流(文件光标)的读写位置
失败:-1
其他
int remove(const char *pathname);删除文件
int rename(const char *oldpath, const char *newpath);
把oldpath的文件名改为newpath
ANSI C 标准采用“缓冲文件系统”处理数据文件。所谓缓冲文件系统是指系统自动地在内存区为程序中每一个正在使用的文件开辟一个文件缓冲区从内存向磁盘输出数据必须先送到内存中的缓冲区,装满缓冲区后才一起送到磁盘去。如果从磁盘向计算机读入数据,则一次从磁盘文件将一批数据输入到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(给程序变量)
int fflush(FILE *stream);
更新缓冲区,让缓冲区的数据立马写到文件中
预处理器
指令 | 描述 |
---|---|
#define | 定义宏 |
#include | 包含一个源代码文件 |
#undef | 取消已定义的宏 |
#ifdef | 如果宏已经定义,则返回真 |
#ifndef | 如果宏没有定义,则返回真 |
#if | 如果给定条件为真,则编译下面代码 |
#else | #if 的替代方案 |
#elif | 如果前面的 #if 给定条件不为真,当前条件为真,则编译下面代码 |
#endif | 结束一个 #if……#else 条件编译块 |
#error | 当遇到标准错误时,输出错误消息 |
#pragma | 使用标准化方法,向编译器发布特殊的命令到编译器中 |
ANSI C预定义了许多宏,如__DATE__,__TIME__等等
defined运算符
当需要把一个宏的参数转换为字符串常量时,则使用字符串常量化运算符(#)
标记粘贴运算符(##)
#include <stdio.h>
#define tokenpaster(n) printf ("token" #n " = %d",
**token##n**)
int main(void)
{
int token34 = 40;
tokenpaster(34);
return 0;
}
token##n 被解释成了token34
错误处理
errno、perror() 和 strerror()
C 语言提供了 perror() 和 strerror() 函数来显示与 errno 相关的文本消息。
- perror() 函数显示您传给它的字符串,后跟一个冒号、一个空格和当前 errno 值的文本表示形式。
- strerror() 函数,返回一个指针,指针指向当前 errno 值的文本表示形式。
可变参数
使用 stdarg.h 头文件,该文件提供了实现可变参数功能的函数和宏。具体步骤如下:
- 定义一个函数,最后一个参数为省略号,省略号前面可以设置自定义参数。
- 在函数定义中创建一个 va_list 类型变量,该类型是在 stdarg.h 头文件中定义的。
- 使用 int 参数和 va_start 宏来初始化 va_list 变量为一个参数列表。宏 va_start 是在 stdarg.h 头文件中定义的。
- 使用 va_arg 宏和 va_list 变量来访问参数列表中的每个项。
- 使用宏 va_end 来清理赋予 va_list 变量的内存。
#include <stdio.h>
#include <stdarg.h>
double average(int num,...)
{
va_list valist;
double sum = 0.0;
int i;
/* 为 num 个参数初始化 valist */
va_start(valist, num);
/* 访问所有赋给 valist 的参数 */
for (i = 0; i < num; i++)
{
sum += va_arg(valist, int);
}
/* 清理为 valist 保留的内存 */
va_end(valist);
return sum/num;
}
int main()
{
printf("Average of 2, 3, 4, 5 = %f\n", average(4, 2,3,4,5));
printf("Average of 5, 10, 15 = %f\n", average(3, 5,10,15));
}
二进制运算
符号 | 描述 | 运算规则 |
---|---|---|
& | 与 | 两个位都为1时,结果才为1 |
或 | ||
^ | 异或 | 两个位相同为0,相异为1 |
~ | 取反 | 0变1,1变0 |
<< | 左移 | 各二进位全部左移若干位,高位丢弃,低位补0 |
>> | 右移 | 各二进位全部右移若干位,对无符号数,高位补0,有符号数,各编译器处理方法不一样,有的补符号位(算术右移),有的补0(逻辑右移) |
例如:
void prtBin(unsigned int number){
unsigned mask = 1u<<31;
for(;mask;mask>>=1){
printf("%d",number & mask?1:0);
}
printf("\n");
}