C语言中二进制数、八进制数和十六进制数的表示:
- 二进制:二进制由 0 和 1 两个数字组成,使用时必须以0b或0B(不区分大小写)开头。例如:
0b101
、0B001
注意:标准的C语言并不支持二进制写法,有些编译器自己进行了扩展,才会支持二进制数字- 八进制:八进制由 0~7 八个数字组成,使用时必须以0开头(注意是数字 0,不是字母 o),例如:
015
(十进制的13)、0177777
(十进制的65535)- 十六进制:十六进制由数字 0~9、字母 A~F 或 a~f(不区分大小写)组成,使用时必须以0x或0X(不区分大小写)开头,例如:
0X2A
(十进制的43)、0xffff
(十进制的65535)
类型 | 类型说明符 | 字节 | 数字范围 |
---|---|---|---|
字符型 | char | 1 | C字符集 |
基本整型 | int | 4 | -32768~32767 |
短整型 | short int | 2 | -32768~32767 |
长整型 | long int | 4 | -214783648~-214783647 |
无符号整型 | unsigned int | 4 | 0~65535 |
无符号长整型 | unsigned long | 4 | 0~4294967295 |
单精度实型 | float | 4 | 10-38~1038 |
双精度实型 | double | 8 | 10-308~10-308 |
数组:按序排列的同类数据元素的集合
类型说明符 数组名[数组长度];
类型说明符 数组名[行数][列数];
多维数组以此类推char 数组名[数组长度];
C语言没有字符串类型,字符串通常用字符数组表示数组定义:
类型说明符 数组名[长度];
数组引用:
一维数组数组名[索引];
二维数组数组名[行索引][列索引];
注:索引都是从0开始
数组赋值:
1.在定义的时候赋初值:int a[10]={1,2,3,4,5,6,7,8,9,10};
或int a[]={1,2,3,4,5,6,7,8,9,10};
2.先定义,再赋值:int a[10];a = {1,2,3,4,5,6,7,8,9,10};
字符数组赋值:
1.char Hello[] = {'H','e','l','l','o'};
2.char Hello[] = "Hello";
注:字符数组第二种赋值方式比第一种方式多占一个字符,因为第二种方式会在字符数组中结尾添加一个\0
作为字符串结束符提示:数组赋值时,如果给定值数量小于数组长度,系统默认填充0
示例:
#include
int main() {
//=====================一维数组===============
int a[5] = {1, 2}; // a={1,2,0,0,0}
int b[] = {1, 2, 3, 4, 5};// b={1,2,3,4,5}
int c[10];// 没有赋初始值系统会自动赋值一个无意义的数字,可以自行printf输出查看
printf("a第二个元素:%d\nb第一个元素:%d\n", a[1], b[0]);
//=====================二维数组===============
int aa[2][3] = {1, 2, 3, 4, 5, 6};// C语言是按行编址,所以可以这样赋值
int bb[2][3] = {
{1, 2, 3},
{4, 5, 6}
};
//aa和bb这两个数组是相同的
printf("aa第1行第1列元素:%d\n", aa[0][0]);
printf("bb第1行第2列元素:%d\n", bb[0][1]);
//=====================字符串===============
char name[8] = {'x', 'i', 'a', 'o', 'm', 'i', 'n', 'g'};
char name2[] = "xiaohong";
printf("第一个名字:%s第二个名字:%s", name, name2);
return 0;
}
C语言中常量的定义有两种方式,假如我们要定义一个int类型的常量TEMP,值为1:
#define TEMP = 1
const int TEMP = 1
注意:自增和自减跟赋值运算结合的时候如果运算符在左边,会先进行自增或自减运算,请看下面例子:
void test1(){
int a = 1;
int b = ++a; //结果是b=2
}
void test2(){
int a = 1;
int b = a++; //结果是b=1
}
C语言中非0为真
0011 & 1111 = 0011
0011 | 1111 =1111
~1111 =0000
0011 ^ 1111 =0011
01100100 << 2 = 10010000
01111111 >> 4 = 00000111
11111111 >> 4 = 11111111
定义:由表达式和分号组成的语句:x + y = z;
定义:函数名、实际参数和分号组成:函数名(参数);
// 用法
if (条件表达式){
// 条件满足
要执行的语句
}
// 用法
if (条件表达式){
// 条件满足
要执行的语句
}else{
// 条件不满足
要执行的语句
}
// 用法
if (条件表达式1){
// 满足条件表达式1
要执行的语句;
}else if (条件表达式2) {
// 满足条件表达式2
要执行的语句;
}else if (条件表达式3) {
// 满足条件表达式3
要执行的语句;
}
...
else if (条件表达式n) {
// 满足条件表达式n
要执行的语句;
}else{
// 所有条件表达式都不满足
要执行的语句;
}
switch(表达式)
{
case 常量1:
// 如果表达式的值等于常量1,执行下面的语句1
语句1 ;
break;
case 常量2:
// 如果表达式的值等于常量2,执行下面的语句2
语句2;
break;
...
case 常量n:
// 如果表达式的值等于常量n,执行下面的语句n
语句n;
break;
default:
// 默认执行的语句,如果没有通过上面的开关语句退出,就会执行下面的语句n+1
语句n+1;
//break; // default可以省略break;因为它本身就是最后执行,执行完就会退出开关语句。
}
注:switch语句如果没有break会一直向下执行直到结束。
结构:
for (表达式1;表达式2;表达式3){
语句;
}
循环逻辑:
step1:先执行表达式1
step2:然后执行表达式2,
step3:如果step2结果为真,执行语句,否则退出循环
step4:如果step3没有退出循环,则执行表达式3
step5:重复执行step2-step4直至循环退出
//用法
for (循环变量赋初值;循环条件;循环变量增量){
执行语句;
}
条件循环语句,当满足循环条件的情况下循环执行
//用法
while (循环条件){
执行语句;
}
与while循环的区别:do…while会先执行一遍循环体里面的语句,再进行条件判断,也就是说,do…while至少会执行一次循环体中的语句
//用法
do{
执行语句;
}while (循环条件);
//用法
int main(){
int a=1;
int b=5;
loop: if (a<b){
printf("%d\n",a);
a++;
goto loop;
}
return 0;
}
输出结果:
1
2
3
4
说明:goto语句一般用于跟if语句结合形成循环结构,需要先定义一个标志符(loop),表示goto转向到哪个地方。
定义:将多个语句用大括号括起来组成一个复合语句
{
int a = 1;
a++;
int b = a + 1;
}
定义:只有分号组成的语句称为空语句
;
根据三角形的三条边求出面积:S= p ( a − p ) ( b − p ) ( c − p ) \sqrt{p(a-p)(b-p)(c-p)} p(a−p)(b−p)(c−p)
S:面积 p:周长的1/2 a,b,c:三角形的三条边长
#include "stdio.h"
#include "math.h"
int main(){
float a;
float b;
float c;
float area;
float p;
printf("请输入构成三角形的三条边的长度:");
scanf("%f,%f,%f", &a, &b, &c);
p = (a+b+c)/2;
area = sqrt(p*(a-p)*(b-p)*(c-p));
printf("三角形面积是:%f",area);
return 0;
}
#include
#include "math.h"
int main() {
float a,b,c;
float p,x1,x2;
printf("请输入一元二次方程的3个系数a,b,c:ax^2+bx+c=0(a≠0)\n");
scanf("%f,%f,%f",&a,&b,&c);
p = sqrt(b*b-4*a*c);
x1 = (-b+p)/(2*a);
x2 = (-b-p)/(2*a);
printf("方程的解为:x1=%f,x2=%f",x1,x2);
return 0;
}
函数是实现了某种功能的代码块
类型标识符 函数名() {
声明部分;
语句;
}
类型标识符 函数名(形参1,形参2,形参3...形参n) {
声明部分;
语句;
}
//void HelloWorld();
//int FindMax(int a,int b);
//上面是对函数进行声明,函数的调用必须先定义,否则编译不通过,如果定义在调用函数之后,需要先声明
void HelloWorld() {
printf("Hello World!");
}
int FindMax(int a, int b) {
int max;
max = a >= b ? a : b;
return max;
}
int main(){
HelloWorld();
int a = 5;
int b = 10;
int c;
c = FindMax(a, b);
printf("\n最大数为:%d\n", c);
return 0;
}
函数返回值是一个类型与函数声明中定义的返回类型相同的值,如果函数声明中没有定义返回类型,则默认为 int 类型。
例如,下面是一个简单的 C 函数,它返回一个整数值:
int max(int a, int b)
{
if (a > b) {
return a;
} else {
return b;
}
}
在这个例子中,函数 max() 定义了两个 int 类型的参数 a 和 b,并在函数体内部判断它们的大小关系。如果 a 大于 b,则函数返回 a 的值;否则,函数返回 b 的值。
另外,如果函数声明中定义了 void 类型的返回值,则表示函数不会返回任何值。在这种情况下,函数体内部不能使用 return 语句返回值。例如:
void print_hello()
{
printf("Hello, world!\n");
}
在这个例子中,函数 print_hello() 不需要返回任何值,因此声明中定义的返回类型为 void。
- 调用的一般形式为:
函数名(实参);
- 被调用函数的声明和函数原型:在主调函数中调用某函数之前应对该被调函数进行说明(声明),这与使用变量之前要先进行变量说明是一样的。在主调函数中对被调函数作说明的目的是使编译系统知道被调函数返回值的类型,以便在主调函数中按此种类型对返回值作相应的处理。
其一般形式为:类型说明符 被调函数名(类型 形参,类型 形参...);
或类型说明符 被调函数名(类型,类型...);
作用域:表示一个变量起作用的范围,例如:
{ int a = 1; //a的作用域就是这个代码块,在代码块外部就无法访问变量a }
extern
做全局变量说明,声明某个变量是全局变量,然后才能使用;在全局变量定义之后的函数中使用全局变量,可以省略extern
关键字,不做全局变量说明也可以使用。int a = 5; // 此处a为全局变量
int main(void){
int extern a; // 全局变量说明,声明a是一个全局变量,此处在a定义之后,可以省略该说明
printf("%d", a); //输出结果为5
}
int main(void){
int a = 5; // 这是一个局部变量,a的作用域范围是main函数内,在函数外无法使用
print("%d", a);
a++;
}
print("%d", a);//全局作用域内找不到变量a,编译不通过
static
关键字声明为静态变量。// 定义一个自增函数,初始化局部静态变量a为0,每调用一次,a自增1
int Add() {
static int a = 0;
a++;
return a;
}
int main(){
print("%d", Add());// 输出结果为1
print("%d", Add());// 输出结果为2
return 0;
}
register
关键字声明。#include "stdio.h"
// 这是一个计算n的阶乘的函数,将局部变量i和f声明为寄存器变量
int fac(int n) {
register int i, f = 1;
for (i = 1; i <= n; i++) {
f = f * i;
}
return f;
}
int main() {
int i;
for (i = 0; i <= 5; i++) {
printf("%d!=%d\n", i, fac(i));
}
return 0;
}
预处理是指在进行编译的第一遍扫描(词法扫描和语法分析)之前所作的工作。预处理是C语言的一个重要功能,它由预处理程序负责完成。
C语言提供了多种预处理功能,如宏定义、文件包含、条件编译等。
C语言可以使用
#define
定义宏(类似常量),程序在编译处理时会把源程序中所有的宏名替换成宏定义的结果。
宏定义是由源程序中的宏定义命令完成的。宏代换是由预处理程序自动完成的。
#define 标识符 字符串
(“字符串”可以是常数、表达式、格式串等)所有出现在源程序中的宏名都会替换成宏定义的字符串
例如:#include
#define PI 3.1415926 #define M (a+a) int main(void) { double a = 1.0; double b; b = 2*M + PI; // 等同于2*(a+a) + 3.1415926 printf("%f", b); return 0; }
#define 宏名(形参1,形参2,形参3,...形参n) 字符串
(“字符串”可以是常数、表达式、格式串等)类似于定义一个匿名函数
>#include <stdio.h> #define S(x,y) x*y // S表示矩形面积,x,y分别表示长宽 int main(void) { double a = 3.0,b = 4.0; double s; s = S(a,b); // 等同于a*b printf("%f", s); return 0; }
文件包含命令的功能是把指定的文件插入该命令行位置取代该命令行,从而把指定的文件和当前的源程序文件连成一个源文件。
文件包含的形式为:#include "文件名"
或#include <文件名>
上面两种形式的区别:使用尖括号表示在包含文件目录中去查找(包含目录是由用户在设置环境时设置的),而不在源文件目录去查找;使用双引号则表示首先在当前的源文件目录中查找,若未找到才到包含目录中去查找。
预处理程序提供了条件编译的功能。可以按不同的条件去编译不同的程序部分,因而产生不同的目标代码文件。
条件编译有以下三种形式:
#define
命令定义过则对程序段 1 进行编译;否则对程序段 2 进行编译。#ifdef 标识符
程序段 1
#else
程序段 2
#endif
#define
命令定义过则对程序段 1 进行编译,否则对程序段 2 进行编译。#ifndef 标识符
程序段 1
#else
程序段 2
#endif
#if 常量表达式
程序段 1
#else
程序段 2
#endif
指针是指存储单元的地址,例如定义一个变量
int a = 1;
指向变量a的指针就是a在内存中的地址。
类型说明符* 变量名;
类型说明符表示这个指针指向的变量类型,换句话说这个指针变量的值必须是一个什么类型的变量的地址
例如:
int* p1; //定义一个int类型的指针变量,指向的变量类型也必须是int
char* p2; //定义一个char类型的指针变量,指向的变量类型也必须是char
double* p3; //定义一个double类型的指针变量,指向的变量类型也必须是double
&变量名
表示取变量的地址,就是获取变量的指针int a = 123; int* p = &a; //取变量a的地址赋值给指针变量p
*指针变量
表示取指向的变量的值int a = 123; int* p = &a; //取变量a的地址赋值给指针变量p printf("%d",*p); //输出123,*p表示取a的值
数组的指针是指数组的首地址。
名词解释:一个数组是由连续的一块内存单元组成的。数组名就是这块连续内存单元的首地址,也是数组中第一个元素的地址。int array[] = {1,2,3,4,5,6}; int* pA = array; // 数组名就是数组的指针 int* pB = &array[0]; // 数组的第一个元素的地址就是数组的指针
指针pA和指针pB是相等的
一个数组的元素值为指针则是指针数组。
定义方式:类型说明符* 数组名[数组长度]
(跟普通数组定义方式相同,唯一区别是*)int main() { int a=1,b=2,c=3,d=4,e=5; int* Int[5] = {&a,&b,&c,&d,&e}; // 这是一个整型指针数组 // 字符串在C语言中是字符数组,所以一个字符串相当于一个字符数组,字符串本身就等于字符数组的指针(首地址) char* String[] = {"Test1","Test2","Test3","Test4","Test5"}; // 这是一个字符型的指针数组 for (int i = 0; i < 5; ++i) { printf("%p\n",String[i]); // 这里输出的就是每个字符串的指针 } return 0;
C语言中是没有字符串类型的,C语言中的字符串都是用字符数组进行存储
字符串的指针就是字符数组的指针,也就是字符数组的首地址
C语言字符串的两种定义形式:
char string[] = {'H','e','l','l','o','\0'};
或char string[] = "Hello";
char* string = "Hello";
(等价于{'H','e','l','l','o','\0'}
)在C语言中,一个函数总是占用一段连续的内存区,而函数名就是该函数所占内存区的首地址(函数指针)。
类型说明符 (*指针变量名)(实参类型);
int (*p)(); // 定义一个函数指针p
int Function(){
printf("test");
}
p = Function; // 将Function函数的入口地址赋值给函数指针变量p
注意:函数指针的定义区别于变量指针
(*指针变量名) (实参表);
int FindMax(int a, int b){
return a > b ? a : b;
}
int main() {
int (*p)(int, int) = FindMax;
int max = p(5,10);
printf("%d",max);
return 0;
}
函数类型是指针的函数就是指针型函数(函数类型是指函数返回值的类型)
定义:类型说明符* 函数名(参数){ 执行语句; return 对应类型的指针; }
例:下面定义了指针型函数,作用是随机生成一个数组,返回数组的指针
#include
#include
#include
int* GetNumber(){
static int array[10];
srand((unsigned)time(NULL));
for (int i = 0; i < 10; ++i) {
array[i] = rand();
printf("%d\n",array[i]);
}
return array;
}
int main() {
int* p = GetNumber();
printf("===================================\n");
for (int i = 0; i < 10; ++i) {
printf("%d\n",p[i]);
}
指向指针的指针,就是字面意思,假如有个变量a,变量a的指针用p1表示,将p1赋值给一个变量b,变量b的指针用p2表示,现在将p2赋值给一个变量c,变量c就是指向指针的指针。
int a = 2333; int* b = &a; int** c = &b;
要访问指向指针的指针的值,要使用**,如上面的指针c,访问方式为
**c
结构体跟一些面向对象的语言(Python、C#、Java)中的类概念相似,就是一组数据由多个成员数据组成,成员数据可以是基本类型或者构造类型,在使用结构体之前必须先进行定义。
结构体是由多个不同数据类型的成员组成的数据类型。结构体中的每个成员可以有不同的数据类型和命名。使用结构体可以将多个不同数据类型的信息组合成一个单一的逻辑单元,从而方便地进行操作。
- 定义结构体关键字:
struct
- 定义形式:
struct 结构名 {成员数据};
// 下面定义了一个名为Person的结构体,Person包含有一个人的姓名、年龄、性别、身高、住址信息
struct Person{
char* name;
int age;
char sex;
double height;
char address[200];
};
结构名.变量名
或(*结构指针).变量名/(*结构指针)->变量名
struct Person{
char* name;
int age;
char sex;
double height;
char address[200];
};
int main() {
struct Person man; // 结构体变量实例化
struct Person woman; // 结构体变量实例化
struct Person* pW = &woman; // 实例化一个结构体指针变量
man.name; // 结构体变量直接表示
man.sex;
(*pW).name; // 结构体指针变量表示
pW->sex; // 结构体指针变量表示
return 0;
}
#include
#include
// 下面定义了一个名为Person的结构体,Person包含有一个人的姓名、年龄、性别、身高、住址信息
struct Person{
char* name;
int age;
char sex;
float height;
char address[200];
};
int main() {
struct Person man;
struct Person woman;
struct Person* pW = &woman;
man.name = "小明"; // 结构体变量赋值
man.sex = 'M';
man.age = 18;
man.height = 1.78f;
strcpy(man.address,"四川省成都市");
(*pW).name = "小红"; // 结构体变量赋值
(*pW).sex = 'W';
pW->age = 19;
pW->height = 1.68f;
strcpy(pW->address,"四川省绵阳市"); // 数组类型不能直接赋值
printf("姓名:%s\n年龄:%d\n性别:%c\n身高:%.2fm\n地址:%s\n",man.name,man.age,man.sex,man.height,man.address);
printf("==============================================================================================\n");
printf("姓名:%s\n年龄:%d\n性别:%c\n身高:%.2fm\n地址:%s\n",woman.name,woman.age,woman.sex,(*pW).height,pW->address);
return 0;
}
共用体是一种特殊的结构体,其所有成员共享相同的内存空间。共用体中的每个成员可以有不同的数据类型,但是它们共享相同的内存空间,因此只能同时存在一个成员的值。共用体的主要用途是在不同的数据类型之间进行类型转换或节省内存空间。
- 定义结构体关键字:
union
- 定义形式:
union 共用体名 {成员数据};
#include
#include
union data {
int i;
float f;
char str[20];
};
int main() {
union data mydata; // 实例化一个共用体变量
mydata.i = 10;
printf("mydata.i = %d\n", mydata.i);
mydata.f = 3.14f;
printf("mydata.f = %f\n", mydata.f);
strcpy(mydata.str, "Hello");
printf("mydata.str = %s\n", mydata.str);
return 0;
}
在这个例子中,我们定义了一个名为data
的共用体,包含一个整型变量i
、一个浮点型变量f
和一个字符数组str
。在main
函数中,我们定义了一个mydata
的共用体变量,可以用来存储int
、float
或char
类型的数据。
由于所有成员变量共享同一块内存空间,因此在设置
mydata.f
和mydata.str
时,mydata.i
的值被覆盖了。这也是共用体的一个特点:在任意时刻,只能有一个成员变量是有效的。
主要用途:在不同的数据类型之间进行类型转换或节省内存空间。
#include
#include
union data {
int i;
float f;
char* s;
char c;
};
int main() {
union data temp; // 定义一个共用体temp
temp.i = 10;
printf("temp = %d\n",temp.i);
printf("data中i的内存地址:%p\n",&temp.i);
printf("data中f的内存地址:%p\n",&temp.f);
printf("data中s的内存地址:%p\n",&temp.s);
printf("data中c的内存地址:%p\n",&temp.c);
// 可以看出共用体的所有成员指向的是同一块内存空间
printf("=========================================================\n");
temp.s = "测试";
printf("temp = %s\n",temp.s);
printf("data中i的内存地址:%p\n",&temp.i);
printf("data中f的内存地址:%p\n",&temp.f);
printf("data中s的内存地址:%p\n",&temp.s);
printf("data中c的内存地址:%p\n",&temp.c);
printf("=========================================================\n");
temp.f = 3.14159f;
printf("temp = %f\n",temp.f);
printf("data中i的内存地址:%p\n",&temp.i);
printf("data中f的内存地址:%p\n",&temp.f);
printf("data中s的内存地址:%p\n",&temp.s);
printf("data中c的内存地址:%p\n",&temp.c);
printf("=========================================================\n");
//通过上面的例子,如果把temp看做一个没有定义类型的变量,那么他就是个可变类型的变量
return 0;
}
枚举(Enumeration)是一种自定义的数据类型,它允许定义一组命名的常量。枚举类型的变量只能赋值为枚举列表中的一个值,这些值被称为枚举常量。枚举类型是一种非常方便的方式来组织和描述常量。
- 定义枚举关键字:
enum
- 定义枚举的形式:
enum 枚举名称 {枚举常量列表};
(枚举常量的值被认为是int类型或者unsigned int类型,默认枚举变量值从0开始递增)
enum color {
RED,
GREEN,
BLUE
};
/*上面定义了一个三种颜色的枚举,三种枚举默认值为RED=0,GREEN=1,BLUE=2*/
// 下面定义一个性别的枚举,并给枚举值进行自定义
enum sex {
MAN = 1,
WOMAN = 2
}
枚举常用来定义一组常量选项
#include
#include
enum week {
Mon,
Tue,
Wed,
Thu,
Fri,
Sat,
Sun
};
int main() {
enum week today;
today = Mon;
switch (today) {
case Mon:
printf("今天是周一");
break;
case Tue:
printf("今天是周二");
break;
case Wed:
printf("今天是周三");
break;
case Thu:
printf("今天是周四");
break;
case Fri:
printf("今天是周五");
break;
case Sat:
printf("今天是周六");
break;
case Sun:
printf("今天是周日");
break;
}
return 0;
}
C语言常用的内存管理函数有四个:
malloc
、calloc
、realloc
、free
其中申请空间的函数是malloc
、calloc
;重新调整空间大小的函数是realloc
;释放空间的函数是free
作用:用于在堆上分配指定大小的内存空间,内容随机,函数原型:
void* malloc(size_t size);
参数:
- size:分配空间的大小(字节)
返回值:分配的内存空间的首地址,分配失败返回NULL空指针
注意:返回值类型为void*,使用时需要转换成对应类型
下面是一个例子:分配一块空间存储指定个数的数字,并对数字求和
#include
#include
int main() {
int* ptr; // 定义一个指针变量
int n, sum = 0; // 初始化元素个数与元素总和
printf("输入要保存的元素个数: ");
scanf("%d", &n);
ptr = (int*) malloc(n * sizeof(int)); // 分配一块足够存储n个int类型数字的内存空间,将指针强制转换为int类型
if(ptr == NULL) {
printf("内存空间分配失败!\n");
exit(1);
}
printf("输入保存的元素:\n");
for(int i = 0; i < n; i++) {
scanf("%d", &ptr[i]);
sum += ptr[i];
}
printf("所有元素累加总和为:%d\n", sum);
free(ptr);// 释放内存空间ptr
return 0;
}
作用:用于在堆上分配指定数量和大小的内存空间,内容初始化为0。
其函数原型为:void* calloc(size_t num, size_t size);
参数:
- num:分配空间块数(需要分配多少块空间)
- size:每块空间的大小(字节)
返回值:分配的内存空间的首地址,分配失败返回NULL空指针
注意:返回值类型为void*,使用时需要转换成对应类型
同样使用上面的例子:
#include
#include
int main() {
int* ptr; // 定义一个指针变量
int n, sum = 0; // 初始化元素个数与元素总和
printf("输入要保存的元素个数: ");
scanf("%d", &n);
ptr = (int*) calloc(n, sizeof(int)); // 分配n块足够存储1个int类型数字的内存空间,将指针强制转换为int类型
if(ptr == NULL) {
printf("内存空间分配失败!\n");
exit(1);
}
printf("输入保存的元素:\n");
for(int i = 0; i < n; i++) {
scanf("%d", &ptr[i]);
sum += ptr[i];
}
printf("所有元素累加总和为:%d\n", sum);
free(ptr);// 释放内存空间ptr
return 0;
}
作用:用于重新分配已分配内存的大小。其函数原型为:
void* realloc(void* ptr, size_t size);
参数:
- ptr:原内存空间地址
- size:重新分配内存空间大小
返回值:分配的内存空间的首地址,分配失败返回NULL空指针
注意:返回值类型为void*,使用时需要转换成对应类型说明:
realloc
重新分配是在原地址的基础上进行调整,如果是扩大空间大小,当新的空间大小超过了原空间所能扩展的范围(比如a空间占了4个字节,现在要把a空间扩展到8个字节,而在这一块连续的内存中,第7个字节已经被分配出去了,那么这块空间最大只能是6个字节了),系统会重新找一块足够大的空间来作为新空间,然后将原本空间中的数据拷贝过来,释放原本的空间,也就是指针会进行改变,值不会发生变化;如果是缩小空间大小,就会释放原空间调整之后的内存空间。
同样使用上面例子做修改:
#include
#include
int main() {
int* ptr; // 定义一个指针变量
int n,m, sum = 0; // 初始化元素个数与元素总和
printf("输入要保存的元素个数: ");
scanf("%d", &n);
ptr = (int*) calloc(n, sizeof(int)); // 分配n块足够存储1个int类型数字的内存空间,将指针强制转换为int类型
if(ptr == NULL) {
printf("内存空间分配失败!\n");
exit(1);
}
printf("输入保存的元素:\n");
for(int i = 0; i < n; i++) {
scanf("%d", &ptr[i]);
sum += ptr[i];
}
m = n+2;
ptr = (int*)realloc(ptr,m*sizeof(int)); // 重新分配一块足够存储m个int类型数字的内存空间
printf("输入新增的元素:\n");
for(int i = n; i < m; i++) {
scanf("%d", &ptr[i]);
sum += ptr[i];
}
printf("所有元素累加总和为:%d\n", sum);
free(ptr);// 释放内存空间ptr
return 0;
}
作用:用于释放已分配的内存空间。其函数原型为:
void free(void* ptr);
参数:
- ptr:需要释放的空间地址
返回值:没有返回值
C语言允许在一个结构体中以位(Bit)为单位来指定其成员长度,这种以位为单位的结构体成员称为“位段”或者“位域”。位域只能是int、unsigned int、signed int类型。int默认是有符号整型(signed)。
位域的主要目的:节省内存空间,比如开关控制只需要0和1,那么只需要1位就能表示二进制0和1,一个字节有8位,使用位域就可以只是用一个字节中的其中1位。
struct 位域名称 {
位域列表;
}
#include
struct Date{
unsigned int year;
unsigned int month;
unsigned int day;
};
struct Date2{
unsigned int year : 14;
unsigned int month : 4;
unsigned int day : 6;
};
int main() {
printf("Date占用字节数:%llu\n", sizeof(struct Date));
printf("Date2占用字节数:%llu\n", sizeof(struct Date2));
return 0;
}
输出结果:
Date占用字节数:12
Date2占用字节数:4
从以上结果便能看出,使用位域可以节省内存空间。
注意:位域的位数不能超过其依附的基本类型的最大位数,例如一个unsigned int类型的成员,他有4个字节,一个字节是8位,它最大只能存储32位,位域的位数就不能超过32(不同的编译器基本类型占用空间大小不一致)
文件是一个有序数据集,数据集的名称叫文件名。文件分为两种,一种是普通文件,比如txt文件、C语言的源程序文件、头文件等等存在于磁盘上的;另一种是设备文件,比如鼠标、键盘、显示器等等外部设备,都认为是一个文件。
C语言使用一个指针变量指向一个文件,通过操作指针来操作文件。
文件指针的定义:FILE *变量名;
FILE
实际上是系统定义的一个结构体,该结构体中含有文件名、文件状态、文件当前位置等信息(编写程序时不用关心FILE
结构体细节)文件位置指针: 文件位置指针表示的是文件中所处位置的指针(头部、当前位置、末尾等),注意跟文件指针区别开,文件指针指向的是整个文件
NULL
FILE* fopen(const char *path,const char *mode)
选项 | 说明 |
---|---|
r | 只读打开一个文本文件,只允许读数据 |
w | 只写打开一个文本文件,只允许写数据 |
a | 追加写入打开一个文本文件,在文件末尾写数据 |
rb | 以二进制方式打开一个文件,只允许读数据 |
wb | 以二进制方式打开一个文件,只允许写数据 |
#include "stdio.h"
#include "stdlib.h"
FILE *fp = fopen("文件名", "打开模式");
if (fp == NULL) {
printf("文件打开失败!");
exit(1);
}
/* 要执行的文件操作 */
fclose(fp);
文件结束符:
EOF
文件写入的函数需要以写或者读写模式打开文件,文件读取的函数需要以读或者读取的模式打开文件,读取或写入操作之后,位置指针都会向后移动到读取或写入位置的末尾
int fgetc(FILE *file);
fgetc(文件指针);
int fputc(int c, FILE *file);
EOF
fputc('a', 文件指针);
char* fgets(char *Buffer, int MaxCount, FILE *file );
\0
。fgets(数组首地址, 字符串最大长度, 文件指针);
int fputs(const char *str, FILE *file);
fputs(字符串, 文件指针);
size_t fread(void *Buffer, size_t size, size_t count, FILE *file);
fread(内存空间地址, 数据块大小, 数据块数量, 文件指针);
size_t fwrite(const void *Buffer, size_t size, size_t count, FILE *file);
fwrite(数据地址, 数据块大小, 数据块数量, 文件指针);
int fscanf(FILE *file, const char *str, [arg...]);
EOF
fscanf(文件指针, 格式化字符串, 目标地址);
int fprint(FILE *file, const char *str, [arg...]);
\0
)为止。EOF
fprintf(文件指针, 格式化字符串, 目标数据);
void rewind(FILE *file);
rewind(文件指针);
int fseek(FILE *file, long offset, int start);
起始点 | 标识符 | 数字表示 |
---|---|---|
文件头部 | SEEK_SET | 0 |
当前位置 | SEEK_CUR | 1 |
文件尾部 | SEEK_END | 2 |
fseek(文件指针, 偏移量, 起始点);
feof:判断文件位置指针是否处于文件结束位置
int feof(FILE *file);
ferror:检查文件在用各种输入输出函数进行读写时是否出错
int ferror(FILE *file);
clearerr:清除出错标志和文件结束标志,使它们为0值
void clearerr(FILE *file);
#include "stdio.h"
#include "stdlib.h"
struct Student {
char name[20];
int age;
float score;
};
int main() {
FILE *fp = fopen("test.txt", "w+"); // 以读写模式打开一个文件
if (fp == NULL) {
printf("文件打开失败!");
exit(1);
}
fputc('a', fp); // 向文件写入一个字符'a'
rewind(fp); // 将文件位置指针放到文件头部,因为我们刚刚向文件写入了一个字符'a',所以现在文件位置指针指向的文件尾部
char ch = (char)fgetc(fp); // 从文件读取一个字符,现在文件中只有一个'a',读取的字符就是'a'
printf("%c\n",ch);
printf("结束位置:%d\n", feof(fp)); // 看看位置指针是不是在结束位置
fseek(fp,1L,0); // 将文件位置指针手动置于字符'a'后面,读取时也会把指针后移,但是写入的时候失败了,原因暂时未找到!
fputs("this is fputs test", fp); // 向文件中写入字符串,现在文件中的内容应该是"athis is fputs test"
printf("写入出错:%d\n",ferror(fp)); // 查看写入是否出错
rewind(fp); // 位置指针放回文件头部
char string[255]; // 定义一个字符数组用来存放字符串
fgets(string, 255, fp); // 读取文件中的字符串到字符数组string中,遇到换行或文件末尾就结束
printf("%s\n",string); // 输出:athis is fputs test
rewind(fp); // 位置指针放回文件头部
fprintf(fp,"%s %d %f","test", 1, 0.6f); // 现在文件内容是"test 1 0.600000test",因为现在写入的把前面的"athis is fputs "覆盖了
rewind(fp);
char str[255];
int a;
float b;
fscanf(fp,"%s %d %f",str,&a,&b);
printf("str的值:%s\na的值:%d\nb的值:%f",str,a,b);
/*
str的值:test
a的值:1
b的值:0.600000
*/
fclose(fp);
struct Student boys[3]; // 定义一个结构体数组
struct Student boy2[2];
struct Student *pb; // 定义一个结构体指针
pb = boys; // 指向结构体中第一个成员(数组首地址)
FILE *fp1 = fopen("test1.txt", "wb+"); // 以二进制读写模式打开一个文件
if (fp1 == NULL) {
printf("文件打开失败!");
exit(1);
}
for (int i=0;i<3;i++){
scanf("%s %d %f",pb->name,&pb->age,&pb->score); // 这里循环输入学生的信息
pb++; // 指针向后移动,指向下一个boys数组的成员
}
long size = sizeof(struct Student); // 获取结构的大小
fwrite(boys, size,3,fp1); // 向文件中写入3个Student结构
rewind(fp1);
fseek(fp1,size,SEEK_SET); // 位置指针移动到第二个学生的地址
fread(boy2,size,2,fp1); // 读取2个Student大小的数据
for (int i=0; i < 2;i++) {
printf("%s %d %f\n",boy2[i].name,boy2[i].age,boy2[i].score);
}
fclose(fp1); // 关闭文件
return 0;
}