文章目录
前言
呆呆鸭(呀)的c语言的学习笔记
一、数据类型
数据类型分为基本数据类型、派生数据类型和抽象数据类型。
1.基本数据类型
基本数据类型包括整数类型和浮点数类型。
(1)整数型例如int,char(注:char之所以也是整数类型,是因为char字符类型本质是存储在电脑上的整数,只不过通过ASCII码值进行了转换成了字符)。
(2)浮点型例如float,double类型。浮点型既是具有小数点的实数。
2.派生数据类型
派生数据类型包括布尔类型、指针和聚合类型,聚合类型又包括结构体,数组,枚举,联合等
3.抽象数据类型
抽象数据类型(abstract data type,ADT)是一个数据模型及定义在该模型上的一组运算。
抽象数据类型具体包括三部分:数据对象,数据对象上的关系集合,数据对象的基本操作的集合。
数据结构中的线性表,栈,队列,树,图等结构就是一个个不同的抽象数据类型。
二.数组
数组的定义:把具有相同类型的若干变量按有序的形式组织起来。这些按序排列的同类数据元素的集合称为数组。
一维数组
类型说明符 数组名[常量表达式1];
一维数组定义举例
int a[10];
说明int类型数组a有10个元素。
float b[5],c[15];
说明float类型数组b有5个元素,float类型数组c有15个元素。
char ch[20];
说明char类型数组ch有20个元素。
在c语言中没有字符数组这种类型但是可以用char型数组来代替
char c1[] = { 'd', 'a ', 'a', 'c', 's', 'f' }; //普通字符数组
printf("c1 = %s\n", c1); //
//以‘\0’(‘\0’就是数字0)结尾的字符数组是字符串
char c2[] = { 'c', 'd ', 'd', 'r', 'g', 's', '\0' };
printf("c2 = %s\n", c2);
//字符串处理以‘\0’(数字0)作为结束符,后面的'd', 'l', 'l', 'a', 'w'不会输出
char c3[] = { 'c', ' a', 'd', 'a', 'o', 'g', '\0', 'd', 'l', 'l', 'a', 'w', '\0' };
printf("c3 = %s\n", c3);
return 0;
二维数组以及多维数组
类型说明符 数组名[常量表达式1][常量表达式2];
二维数组定义举例
int a[10][10];
说明int类型数组a有100个元素,10行10列。
二维数组初始化:
可以使用初始化列表对二维数组初始化
分行给二维数组赋初值
int a[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};
可以将所有的元素放在一个花括号内,按数组元素在内存中的排序进行赋值
int a[3][4]={1,2,3,4,5,6,7,8,9,10,11,12};
可以对部分元素赋初值
int a[3][4]={{1},{2},{3};
可以对各行中的某一元素赋初值
int a[3][4]={{1},{0,6},{0,0,0,3}};
可以只对某几行赋值
int a[3][4]={{1},{5,6}};
int a[3][4]={{1},{},{9,10}};
二维数组初始化可以省略第一维的长度,但第二维的长度不能省略。
因为二维数组逻辑上是可以看成一个n行m列的矩阵,但是实际计算机存储时是线性存储的,计算机通过
a+x * n * l+m*l的公式进行寻址,a是该数组的首地址,x是该数组每行的元素个数,l是该数组类型的字节长度。
当省略行时
int x[][4]={1,2,3,4,5,6,7,8,9,10,11,12};
可知a既是x数组的首地址,x既是4,l为int类型长度为4.可以通过n行,m列寻址
而当省略列时
int x[3][]={1,2,3,4,5,6,7,8,9,10,11,12};
可知a还是x数组的首地址,但x未知,l为int类型长度为4.不能n行,m列寻址
定义数组时应该注意:
(1)定义数组时下标的个数指明了数组的维数,下标的值说明了数组对应维的长度,各维度的长度相乘的结果就是当前数组的总长度,即数组中元素的个数。
(2)数组下标声明必须是常量表达式,无论是否有确定值,均不可作为下标声明。
(3)数组元素的实际下标从0开始计数。 例如定义了int a[5],则数组元素的下标应该是从0~4进行计数,数组元素应该是从a[0]到a[4]。
三.基础语句
1.scanf()
scanf(格式控制,地址列表);
scanf中的格式声明以%开始,以一个格式字符结束。
scanf函数中用到的格式字符:
格式字符 | 说明 |
---|---|
d,i | 输入有符号的十进制数 |
u | 输入无符号的十进制数 |
o | 输入无符号的八进制数 |
x,X | 输入无符号的十六进制数 |
c | 输入单个字符 |
s | 输入字符串,将字符串送到一个字符数组中,在输入时以非空白字符开始,以第一个空白字符结束。字符串以串结束标志’\0’作为其最后一个字符 |
f | 输入实数,可以用小数形式或指数形式输入 |
e,E,gG | 与f作用相同,e与f,g可以互相替换 |
scanf函数中的用到的格式附加字符:
字符 | 说明 |
---|---|
l | 输入长整型数据(可用%ld, %lo, %lx, %lu)以及double型数据(用 %lf 或者 %le ) |
h | 输入短整型数据(可用%hd, %ho, %hx) |
域宽 | 指定输入数据所占宽度(列数),域宽应为正整数 |
* | 本输入项在读入后不赋给相应的变量a |
特殊的,
在vs高版本,例如vs2022中由于安全性考虑,使用的是scanf_s()而不是scanf()。使用scanf()会报错
当改用scanf_s()后错误消失
2.printf()
printf(格式控制,输出列表);
格式字符
%d 输出一个有符号的十进制整数
%c 输出一个字符
%s 输出一个字符串
%f 输出实数(包括小数点小数的数),单精度,双精度,长双精度
%m.nf 指定数据宽度和小数宽度
%-m.nf 输出的数据向左对齐
%e 以指数形式输出
%o 以八进制整数输出
%x 以十六进制整数输出
...
int inputi=5;
char inputc='a';
char inputs[5]="abcde";
double inputf=1.0;
int inpute=1000000000;
int inputo=13;
int inputx=19;
printf("input1=%d",inputi);
printf("input2=%c",inputc);
printf("input3=%s",inputs);
printf("input4=%f",inputf);
printf("input5=%7.3f",inputf);
printf("input6=%-7.3f",inputf);
printf("input7=%e",inpute);
printf("input8=%o",inputo);
printf("input8=%x",inputx);
3.控制语句
1.if()...else... (条件语句)
2.for()... (循环语句)
3.while()... (循环语句)
4.do...while() (循环语句)
5.continue (结束本次循环语句)
6.break (中止执行switch或循环语句)
7.switch (多分支选择语句)
8.return (从函数返回语句)
9.goto (转向语句,结构化程序中一般不用)
四.函数
1.函数的分类
从函数的定义的角度来看,函数可以分为·主函数
,库函数
和用户自定义函数
。
从函数有无返回值的角度来看,函数可以分为有返回值函数
和无返回值函数
。
从主调函数和被调用函数间是否存在数据的传递角度来看,函数可以分为有参函数
和无参函数
。
2.函数的定义和调用
函数的定义:
类型名 函数名([参数类型说明及列表])
{
局部变量说明
/函数体/
}
函数的声明:
类型名 函数名([参数类型说明及列表]);
函数是通过函数名进行调用的,函数的调用:
函数名([实参表达式1,实参表达式2]);
3.参数传递
(1)形参只有在被调用时才分配内存单元,在调用结束时,即刻释放所分配的内存单元。所以,形参只在函数内部有效。
(2)实参可以是常量,变量,表达式,函数等,函数调用时,实参必须具有确定的值,以便将该值传递给形参。
(3)实参和形参在数量上,类型上,顺序上应保持严格一致,否则会发生"类型不一致"的错误。
(4)实参向形参的数据传递是“值传递”,单向传递,只能由实参传给形参,而不能由形参传给实参。实参和形参在内存中占不同的存储单元,实参无法得到形参的值。因此,形参的变化不会影响实参。(注:利用指针传递地址可以双向改变,因为它传递的是地址不是值)
五.结构体
结构体变量的定义:
struct 结构体名
{
类型 变量名;
类型 变量名;
…
}结构体变量;
先声明结构体类型,再定义该类型的变量。
struct stu
{
char name[8];
int age;
char sex[4];
int score;
};
struct stu student1;
定义一个结构体变量:struct stu student1;
定义一个结构体数组:struct stu student[40];
使用结构体变量中的成员变量:
结构体变量.成员名
student1.name="Zhang San";
六.指针
1.符号认识:
*名为指针运算符(或间接访问符), * p代表存储在指针变量p指向的地址上的值
&名为取地址符,&a是变量a的地址
2.指针的定义,初始化及赋值:
指针的定义:
数据类型名 * 变量名;
例如
int * i_pointer;
定义了一个int类型的指针
int b=0; int* a=&b;
定义了一个int指针a指向int类型的b的地址。
int c=0; int* d; d=&c;
定义了一个int 指针c,并给其赋值d的地址
指针变量的引用:
指针变量在定义且赋值后,可以使用指针变量间接访问指针所指向地址空间的变量。
int num;
int *i_pointer=# //"*"是指针定义运算符,定义了一指针变量
*i_pointer=8; //"*"是间接访问符,说明间接访问指针所指向的变量,即num,该句等同于num=8;该过程又称解引用
指针变量可以作为表达式的组成部分来使用。
int x,y,*x_pointer=&x; /*指针变量x_pointer指向整型变量x,则*x_pointer可代替变量x出现在表达式中*/
y=*x_pointer+5; //等同于y=x+5;
y=++*x_pointer; //存储在x_pointer指向的地址上的值+1后再赋值给y;
y=*x_pointer++; //相当于y=*x_pointer;*x_pointer++;赋值后再自加1;
#include<stdio.h>
int main()
{
int* max,* min,* temp,a,b;
scanf("%d%d",&a,&b);
max=&a;
min=&b;
if(a<b)
{
temp=min;
min=max;
max=temp;
}
printf("max=%d,min=%d",*max,*min);
}
3.数组和指针
(1)指针引用数组
数组元素在地址中是连续的,所以指针只需要指向数组元素的首地址即可访问数组中的任意一个元素。
int scoreArray[10];
int *score_pointer;
score_pointer=scoreArray; //将数组的首地址赋值给指针
(2)指针数组
指针数组就是存储一系列相同指针的数组,指向同一数据类型的指针构成的有序集合。
数据类型名 * 数组名[整型常量表达式];
int *x[10];
定义了一个包含10个整型指针元素的数组,该数组的每一个元素都是一个整型变量的指针。
4.指针和字符串
字符串的引用方式
在c语言中字符串是存放在字符数组中的。
用字符数组存放一个字符串,可以通过数组名和下标引用字符串中的一个字符,也可以通过数组名和格式"%s"输出该整个字符串.
#include<stdio.h>
int main()
{
char a[]="I love world!";
printf("%s",a);
printf("%c",a[3]);
return 0;
}
使用指针引用字符串
#include<stdio.h>-
int main()
{
char *a="I love world!";
printf("%s",a);
printf("%c",*(a+3));
return 0;
}
5.函数和指针
指针函数(指针类型的函数)
函数指针(指向函数的指针)
类比于苹果味的蛋糕和蛋糕味的苹果。看后项词语即可看出其本质。
指针函数就是返回值是指针类型的函数,本质上是一个函数。而函数指针就是指向函数地址的指针,本质上就是个指针。
(1)指针函数
例如:
输入几周几天判断其在一个月里的哪一天
#include<stdio.h>
int *GetDate(int wk,int dy);
int main()
{
int wk,dy;
do
{
printf("Enter week(1-5)day(1-7)\n");
scanf("%d%d",&wk,&dy); //输入1-5的周1-7天
}while(wk<1||wk>5||dy<1||dy>7); //当不满足规定条件时需要重新输入
printf("%d\n",*GetDate(wk,dy));
}
int *GetDate(int wk,int dy)
{
static int calendar[5][7]=
{
{1,2,3,4,5,6,7},
{8,9,10,11,12,13,14},
{15,16,17,18,19,20,21},
{22,23,24,25,26,27,28},
{29,30,31}
}
};
return &calendar[wk-1][dy-1]; //返回数组的地址
(2)函数指针
判断两数大小
#include<stdio.h>
int max(int num1,int num2)
{
if(num1>num2)
return num1;
else
return num2;
}
void main()
{
int (*pointer)();
int num1=10,num2=20;
pointer=max;
printf("max=%d\n",*pointer(num1,num2));
}
6.结构体指针
指向结构体变量的指针
#include<stdio.h>
struct Student
{
int num;
char name[20];
char sex;
int age;
};
struct Student stu[3]={{10101,"ZhangSan",'M',18},{10102,"LiSi",'M',19},{10103,"LiuWu",'W',20}};
int main()
{
struct Student *p;
printf("No. Name sex age\n");
for(p=stu;p<stu+3;p++)
{
printf("%d,%s,%c,%d",p->num,p->name,p->sex,p->age);
}
}
7.用指针处理链表
建立简单静态链表
#include<stdio.h>
struct Student
{
int num;
float score;
struct Student *next;
};
int main()
{
struct Student a,b,c,*head,*p;
a.num=10101;a.score=89.9;
b.num=10102;b.score=90.5;
c.num=10103;c.score=95.3;
head=&a;
a.next=&b;
b.next=&c;
c.next=NULL;
p=head;
do
{
printf("%ld %5.1f\n",p->num,p->score);
p=p->next;
}while(p!=NULL);
return 0;
}
建立动态链表
#include<stdio.h>
#include<malloc.h>
#define LEN sizeof(struct Student)
struct Student
{
long num;
float score;
struct Student *next;
};
int n;
struct Student *creat()
{
struct Student *head;
struct Student *p1,*p2;
n=0;
p1=p2=(struct Student*)malloc(LEN);
scanf("%ld,%f",&p1->num,&p1->score);
head=NULL;
while(p1->num!=0)
{
n=n+1;
if(n==1)
head=p1;
else
p2->next=p1;
p2=p1;
p1=(struct Student *)malloc(LEN);
scanf("%ld,%f",&p1->num,&p1->score);
}
p2->next=NULL;
return (head);
}
void printf(struct Student *head)
{
struct Student *p;
printf("\nNOW,There %d records are:\n",n);
p=head;
if(head!=NULL)
do
{
printf("%ld %5.1f\n",p->num,p->score);
p=p->next;
}while(p!=NULL);
}
int main()
{
struct Student *head;
head=creat();
printf(head);
return 0;
}
七.数据类型重命名
格式:
typedef 类型 定义名;
例如
typedef int SIGNED_INT;
SIGNED_INT就成为了int的同义词了,此时可以用SIGNED_INT定义整型变量。
typedef同样可以用来说明结构体,联合及枚举。
typedef struct
{
数据类型 成员名;
.....
}结构体名;
八.内存动态管理
引用头文件<stdlib.h>
分配内存空间(3个函数1个操作符)
1. malloc函数
函数声明:void *malloc(size_t size);
参数:size是要分配的字节数。
返回值:成功时返回指向新分配内存首地址的指针;失败返回空指针。
作用:按size大小分配内存空间,但不初始化。
2. calloc函数
函数声明:void* calloc( size_t num, size_t size );
参数:num是对象数量;size是单个对象的字节数。
返回值:成功时返回指向新分配内存首地址的指针;失败返回空指针。
作用:分配 num 个大小为 size 的对象的数组,并初始化所有位为0。
3.relloc函数
函数声明:void* realloc( void* ptr, std::size_t new_size );
参数:ptr是指向要被重分配的内存区域的指针;size是数组的新大小。
返回值:成功时返回指向新分配内存首地址的指针;失败返回空指针。
作用:重分配给定的内存区域。
扩张或者收缩ptr所指向的现有内存区域,扩张内存区域中的内容未定义。
或者重新开辟内存区域,复制新旧大小中较小的内存区域。
4.new操作符
作用:new 按照元素个数申请空间,自动检测元素类型,计算所需字节数。
中括号 [ ]:申请多个元素的空间 。
小括号 ( ):申请一个元素的空间,给这个空间初始化。
无括号:申请一个元素的空间,但不初始化。
释放内存空间
free函数
函数声明:void free( void* ptr );
参数:ptr是指向要被释放内存的指针
返回值:无
作用:释放内存,解分配之前由malloc,relloc等分配出的内存。
在实际应用数组的过程中,常常不知道数组需要开辟多少空间,或者数组每次开辟的空间大小不一样。
但是定义数组时input即数组大小必须为常量。不能跟随输入input的变化而变化。
可以通过内存动态管理可以实现动态开辟数组
动态开辟数组实际上是从指针指向的首地址开辟一块空间,在这块空间中分割成input块数量的连续的小单元
使用malloc
#include<stdio.h>
#include<stdlib.h>
int main() {
int input; //input输入想要开辟动态数组的大小
scanf("%d", &input);
int size_all = sizeof(int) * input;//size_all是开辟空间的总大小=单个小单元 int *要开辟动态数组的个数
int* p = (int*)malloc(size_all);
for (int i = 0; i < input; i++) {
scanf("%d", &p[i]);
}
for (int i = 0; i < input; i++) {
printf("%d ", p[i]);
}
free(p);
p = NULL;
}
使用calloc动态开辟数组
#include<stdio.h>
#include<stdlib.h>
int main() {
int input;
scanf("%d", &input);
int size_all = sizeof(int) * input;
int *p=(int *)calloc(input, size_all);
for (int i = 0; i < input; i++) {
scanf("%d", &p[i]);
}
for (int i = 0; i < input; i++) {
printf("%d ", p[i]);
}
free(p);
p=NULL;
}
使用new动态开辟数组
#include<stdio.h>
#include<stdlib.h>
int main() {
int input;
scanf("%d", &input);
int* p = new int[input];
for(int i=0;i<input;i++)
scanf("%d",&p[i]);
for(int i=0;i<input;i++)
printf("%d\n",p[i]);
free(p);
p=NULL;
}
九.对文件的输入输出
文件基本知识
文件分类:
文件分为程序文件和数据文件。
程序文件:包括源文件(后缀为.c),目标文件(后缀为.obj),可执行文件(后缀为.exe)等。文件内容往往是程序代码的文件。
数据文件:文件的内容不是程序,往往是程序的输入输出数据。
文件指针(文件类型指针)
FILE *fp;
文件的打开与关闭:
打开数据文件
fopen(文件路径,使用文件方式);
使用文件方式
文件使用方式 | 含义 | 如果指定的文件不存在 |
---|---|---|
r | 为了输入数据,打开一个已存在的文本文件 | 出错 |
w | 为了输出数据,打开一个文本文件 | 建立新文件 |
a | 向文本文件尾添加数据 | 出错 |
rb | 为了输入数据,打开一个二进制文件 | 出错 |
wb | 为了输出数据,打开一个二进制文件 | 建立新文件 |
ab | 向二进制文件尾添加数据 | 出错 |
r+ | 为了读和写,打开一个文本文件 | 出错 |
w+ | 为了读和写,建立一个新的文本文件 | 建立新文件 |
a+ | 为了读和写,打开一个文本文件 | 出错 |
rb+ | 为了读和写,打开一个二进制文件 | 出错 |
wb+ | 为了读和写,建立一个新的二进制文件 | 建立新文件 |
ab+ | 为读写打开一个二进制文件 | 出错 |
通常将一个fopen的返回值赋给一个指向文件的指针。
二进制(binary),带w的使用方式在指定文件不存在后都是建立新文件
关闭数据文件
在使用完一个文件后应该关闭它,以防止被误用。关闭就是撤销文件信息区和文件缓冲区,使文件指针变量不再指向该文件。
fclose(文件指针);
顺序读写数据文件
读写一个字符的函数
函数名 | 调用形式 | 功能 | 返回值 |
---|---|---|---|
fgetc | fgetc(fp) | 从fp指向的文件读入一个字符 | 读成功,带回所读的字符,失败则返回文件结束标志EOF(即-1) |
fputc | fputc(ch,fp) | 把字符ch写到文件指针变量fp所指向的文件中 | 输出成功,返回值就是输出的字符,失败则返回EOF(即-1) |
读写一个字符串的函数
函数名 | 调用形式 | 功能 | 返回值 |
---|---|---|---|
fgets | fgets(str,n,fp) | 从fp指向的文件读入一个 长度为(n-1)的字符串,存放在字符数组str中。 | 读成功,返回地址str,失败则返回NULL |
fputs | fputs(str,fp) | 把str所指向的字符串写到文件指针变量fp所指向的文件中 | 输出成功,返回0,否则返回非0值 |