C语言基础知识笔记——万字学习记录

Hi,大家好,我是半亩花海。本文主要参考浙大翁恺老师的C语言讲解以及其他博主的C语言学习笔记,进而梳理C语言的基础知识,为后续系统性学习数据结构其他语言等知识夯实一定的基础。(其他博主学习笔记的链接包括:C语言入门基础知识【完整版】_c语言基础知识入门-CSDN博客和C语言基础知识入门大全-CSDN博客以及C语言初阶——手把手教零基础/新手入门(万字心得笔记)_c语言入门自学零基础-CSDN博客)


目录

一、数据类型和运算表达式

1. 数据类型

(1)基本数据类型

(2)构造数据类型

(3)指针类型

(4)空类型

2. 常量

3. 运算表达式

(1)算术运算表达式

(2)关系运算表达式

(3)逻辑运算符

(4)位运算符

二、语句

1. 条件判断语句

(1)简单if语句

(2)if-else语句

(3)多重if-else语句

(4)嵌套if-else语句

(5)switch语句

2. 循环执行语句

(1)while循环

(2)do-while循环

(3)for循环

3. 转向语句

三、函数

1. 函数的定义

2. 函数的参数

3. 全局变量与局部变量

(1)全局变量

(2)局部变量

4. 静态变量与寄存器变量

(1)静态变量

(2)寄存器变量

5. 预处理命令

(1)文件包含

(2)宏定义

6. 常用函数

四、指针

1. 变量的指针和指针变量

(1)变量的指针

(2)指针变量

2. 数组的指针和指针数组

(1)数组的指针

(2)指针数组

3. 字符串的指针

4. 函数的指针&指针型函数

(1)函数的指针

(2)指针型函数

5. 指向指针的指针

五、结构体和公用体

1. 结构体

(1)结构体的定义

(2)结构体的用法

2. 共用体

(1)共用体的定义

(2)共用体的用法

六、动态内存分配

以 malloc() 函数为例

附录1:C语言常见头文件

附录2:文件操作

1. 文件指针

2. 操作文件的函数

(1)打开与关闭

(2)文件读写

(3)文件操作实例


一、数据类型和运算表达式

C语言中二进制数、八进制数和十六进制数的表示:

  • 二进制:二进制由 0 和 1 两个数字组成,使用时必须以 0b 或 0B(不区分大小写)开头。例如:0b101(十进制的 5)、0B111(十进制的 7)。(注意:标准的C语言并不支持二进制写法,有些编译器自己进行了扩展,才会支持二进制数字
  • 八进制:八进制由 0~7 八个数字组成,使用时必须以数字 0 开头。例如:021(十进制的 17)、01010(十进制的 520)。
  • 十六进制:十六进制由数字 0~9、字母 A~F 或 a~f(不区分大小写)组成,使用时必须以0x或0X(不区分大小写)开头。例如:0X2A(十进制的43)、0xffff(十进制的65535)。

1. 数据类型

(1)基本数据类型

  • 整型
  • 字符型
  • 实型(浮点型):单精度(float)和双精度(double)
类型类型说明符字节
字符型char1 Byte
基本整型int4 Byte
短整型short int2 Byte
长整型long int4 Byte
无符号整型unsigned int4 Byte
无符号长整型unsigned long4 Byte
单精度实型float4 Byte
双精度实型double8 Byte

注意:

  • short int 和 long int 是根据编译环境的不同,所取范围不同。
  • C语言 int 的取值范围在于他占用的字节数 ,不同编译器规定不一样(16位占2字节,32位或64位占4字节)。

(2)构造数据类型

  • 枚举类型
  • 数组类型
  • 结构体类型
  • 共用体类型

(3)指针类型

(4)空类型

2. 常量

C语言中常量的定义有两种方式,假如我们要定义一个int类型、名为TEMP、值为1的常量:

  • 预定义命令(#define 定义的标识符常量):
    #define TEMP = 1
  • const关键字(const 修饰的常变量):
    const int TEMP = 1

3. 运算表达式

(1)算术运算表达式

  • 加:+
  • 减:-
  • 乘:*
  • 除:/
  • 取余:%
  • 自增:++
  • 自减:--

注意:

a++和++a(a--和--a)有区别,解释如下:

表达式运算表达式的值
a++给a加1a++为a原来的值
++a给a加1a++为a+1以后的值
a--给a减1a--为a原来的值
--a给a减1a--为a-1以后的值
#include <stdio.h>

int main(){
	int a = 10;
	
	printf("a++=%d\n", a++); //a原来的值
	printf("a=%d\n", a); //a+1后的值
	
	printf("++a=%d\n", ++a); //上述a+1后的值
	printf("a=%d\n", a); //与++a的值一样
	
	return 0;
}

(2)关系运算表达式

  • 等于:==
  • 大于:>
  • 大于等于:>=
  • 小于:<
  • 小于等于:<=
  • 不等于:!=

(3)逻辑运算符

C语言中 0 表示假,1 表示真。

  • 与:&&
  • 或:||
  • 非:!

(4)位运算符

  • 位与:&(对每一位进行逻辑与运算,0表示假,1表示真:0011 & 1111 = 0011)
  • 位或:|(对每一位进行逻辑或运算,0表示假,1表示真:0011 | 1111 =1111)
  • 位非:~(对每一位进行逻辑非运算,0表示假,1表示真:~1111 =0000)
  • 位异或:^(对每一位进行逻辑异或运算,0表示假,1表示真:0011 ^ 1111 =1100)
  • 左移:<<(高位溢出丢弃,低位不足补0:01100100 << 2 = 10010000)
  • 右移:>>
    • 正数:高位补0,低位溢出舍去:01111111 >> 4 = 00000111
    • 负数:高位补1,低位溢出舍去:11111111 >> 4 = 11111111

二、语句

1. 条件判断语句

(1)简单if语句

如果表达式的值为真,则执行其后的语句,否则不执行该语句。 

if(表达式){
    执行代码块;
}

(2)if-else语句

如果表达式的值为真,则执行代码块1,否则执行代码块2。

if(表达式){
    执行代码块1;
}
else{
    执行代码块2;
}

(3)多重if-else语句

if(表达式a){
    执行代码块a;
}
...
else if(表达式b){
    执行代码块b;
}
...
else{
    执行代码块c;
}

(4)嵌套if-else语句

if(表达式a){
    if(表达式b){
        执行代码块;
    }
    else{
        执行代码块;
    }
}
else{
    执行代码块;
}

(5)switch语句

如果表达式的值等于常量1,执行下面的语句1;如果没有通过上面的开关语句退出,就会执行下面的语句n+1。(default可以省略break,因为它本身就是最后执行,执行完就会退出开关语句)。

注意:

switch语句如果没有break一直向下执行直到结束。 

switch(表达式){
	case 常量1: 
		语句1;
		break;

	case 常量2: 
		语句2;
		break;

	case 常量a:
		语句a;
		break;

	default:
		语句b;
		break; //break可以忽略
}

2. 循环执行语句

(1)while循环

计算表达式的值,当值为真(非0)时, 则执行循环体代码块。

while(循环条件){
	执行代码块;
}

举个例子:

int main(){
    int n = 0;
    while(n <= 5) //条件判断
    {
	    printf("a=%d\n", n); //花括号内为语句块
        n++; //计数器放最后
    }
    return 0;
}

注意:

  • 一定要记着在循环体中改变循环变量的值,否则会出现死循环(无休止的执行)。
  • 循环体如果包括有一个以上的语句,则必须用{}括起来,组成复合语句。

(2)do-while循环

它先执行循环中的执行代码块,然后再判断while中表达式是否为真,如果为真则继续循环;如果为假,则终止循环。因此,do-while循环至少要执行一次循环语句。

do{
	执行代码块;
}while(循环条件);

举个例子:

int i = 0;
do{
   i++;
}while(...);

注意:使用do-while结构语句时,while括号后必须有分号。

(3)for循环

for(循环变量赋初值;循环条件;循环变量增量){
	执行代码块;
}

举个例子:

int main(){
    int i = 0;
    for(i = 0; i <= 5; i++){
	    printf("a=%d\n", i);
    }
    return 0;
}

3. 转向语句

(1)break语句

中断语句,一般用于循环结构中,作用是终止循环,当执行到break语句时,会立即退出循环。

(2)continue语句

continue语句一般用于循环结构中,作用是跳过当次循环,当循环语句执行到continue时,不会继续向下执行,会跳过当次循环,直接执行下一次循环

(3)return语句

跳出函数语句,用于跳出函数返回一个值


三、函数

1. 函数的定义

函数是实现了某种功能的代码块。函数的定义方式主要分为无参函数和有参函数。

//无参函数
类型标识符 函数名(){
	声明部分;
	语句;
}

//有参函数
类型标识符 函数名(形参1,形参2,形参3...形参n){
	声明部分;
	语句;
}

示例:下面定义了两个函数,第一个 HelloWorld 是无参函数,功能是输出一个 "Hello World!" 字符串,第二个 FindMax 是有参函数,接收两个 int 类型的参数,返回两个数中最大的那个数。

//函数的调用必须先定义声明,否则编译不通过
void HelloWorld(){ //返回类型为void,表示不返回任何值。
    printf("Hello World!");
}

int FindMax(int a, int b){
    int max; //声明一个整数变量max,用于存储比较后的最大值。
    max = a >= b ? a : b; //使用条件运算符(三元运算符)比较a和b大小并将大值赋给max。
    return max; //返回类型为int,表示返回一个整数值。
}

int main(){
	HelloWorld(); //调用HelloWorld函数,会打印输出"Hello World!"。
    int a = 5; //声明一个整数变量a,并赋值为5。
    int b = 10; //声明一个整数变量a,并赋值为10。
    int c; //声明一个整数变量c,用于存储最大值。
    c = FindMax(a, b); //调用FindMax函数,传入a和b作为参数,将返回值赋给变量c。
    printf("\n最大数为:%d\n", c); //使用%d占位符表示整数
    return 0; //返回0,表示程序正常运行结束。
}

注意:

1)void是一种特殊的数据类型,表示“无类型”或“空类型”。当一个函数的返回类型是void时,该函数执行完毕后不会返回任何结果,或者说返回值为空

2)三元运算符的语法结构如下condition ? expression1 : expression2;

语义如下:如果条件为真(即condition为真),则返回expression1的值;如果条件为假(即condition为假),则返回expression2的值。

2. 函数的参数

  • 形参:形参出现在函数定义中,在整个函数体内都可以使用,离开该函数则不能使用。
  • 实参:实参在主调函数中,是调用函数时传递的参数。
  • 参数传递:函数的参数由主调函数的实参传递给被调函数的形参,因此实参与形参的顺序、类型必须保持一致。

3. 全局变量与局部变量

(1)全局变量

全局变量也称为外部变量,它是在函数外部定义的变量

在全局变量定义之前的函数中使用全局变量,需要使用关键字 extern做全局变量说明,声明某个变量是全局变量,然后才能使用

int a = 5; //此处a为全局变量

int main(void){
	int extern a; //全局变量说明,声明a是一个全局变量,此处在a定义之后,可以省略该说明
	printf("%d", a); //输出结果为5
}

(2)局部变量

局部变量也称为内部变量。局部变量是函数内部定义的变量作用域仅限于函数内部,局部变量只能在函数内部使用,函数外部无法访问

int main(void){
	int a = 5; //这是一个局部变量,a的作用域范围是main函数内,在函数外无法使用
	print("%d", a);
	a++;
}
print("%d", a); //全局作用域内找不到变量a,编译不通过

4. 静态变量与寄存器变量

(1)静态变量

静态变量是在函数调用结束后不消失而保留原值的变量,如果在一个函数调用结束后,希望它保留某个变量的值,就把这个变量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;
}

(2)寄存器变量

寄存器变量是在CPU寄存器中的变量,CPU寄存器可以理解为CPU的内存空间,就像是电脑的内存一样,在寄存器中运算速度非常快,使用 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;
}

5. 预处理命令

(1)文件包含

文件包含命令的功能是把指定的文件插入该命令行位置取代该命令行,从而把指定的文件和当前的源程序文件连成一个源文件(头文件)

文件包含的形式为:#include "文件名"或 #include <文件名>

C语言常见头文件详见附录1

(2)宏定义

C语言可以使用 #define定义宏(类似常量),程序在编译处理时会把源程序中所有的宏名替换成宏定义的结果。宏定义是由源程序中的宏定义命令完成的。宏代换是由预处理程序自动完成的。

  • 无参宏定义:#define 标识符 字符串 (“字符串”可以是常数、表达式、格式串等)
#include <stdio.h>
#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;
}

6. 常用函数

C语言中常用的函数有很多,以下是一些常见的函数:

  • printf():用于输出数据到标准输出设备(通常是控制台),输出完结果后不会自动换行。
  • scanf():用于从标准输入设备(通常是键盘)读取数据
  • putchar():用于输出字符,每次只能输出一个字符,若想输出多个字符则需使用多个putchar()函数,输出完结果后不会自动换行。
  • getchar():用于获取字符,getchar()函数没有参数,且只能接收一个字符,若想输入多个字符则需使用多个getchar()函数。
  • malloc():用于动态分配内存
  • free():用于释放动态分配的内存
  • strcpy():用于复制字符串。
  • strlen():用于获取字符串的长度。
  • strcat():用于将一个字符串追加到另一个字符串的末尾。
  • strcmp():用于比较两个字符串。
  • fopen():用于打开文件
  • fclose():用于关闭文件
  • fprintf():用于将数据输出到文件。
  • fscanf():用于从文件中读取数据。
  • abs():用于计算整数的绝对值。
  • pow():用于计算一个数的指数幂。
  • sqrt():用于计算一个数的平方根。

这些是C语言中常用的函数,还有很多其他函数可以根据具体需求使用。

例如,scanf() 和 printf() 函数如下所示。

# include <stdio.h>

int main(void){
	int a, b;
	
	printf("请输入两个整数:");
	scanf("%d %d", &a, &b); //&a, &b表示变量a, b的地址,&是取地址符
	printf("%d + %d = %d\n", a, b, a + b);
	
	return 0;
}

结果如下:

再例如,putchar() 和 getchar() 函数如下所示。

#include <stdio.h>

int main()
{
    char a = 'A', b = 'W', c = 'M';
    int d = 97, e = 99, f = 101;
    
    putchar(a); //输出字符常量
    putchar(b);
    putchar(c);
    putchar(d); //输出整型常量,通过ASCII码值将int类型的数值转换为字符
    putchar(e);
    putchar(f);
    putchar('\n'); //putchar()函数不能自动换行
    
    putchar('\101'); //输出字符A
    putchar('\''); //输出单撇号,括号中的 \' 是转义字符,代表单撇号',
    putchar('\015'); //八进制数15等于十进制数13,13的ASCII码值表示的是回车
}

结果如下:

#include <stdio.h>

int main()
{
    char a, b, c;
    
    a = getchar();
    b = getchar();
    c = getchar();
    
    putchar(a);
    putchar(b);
    putchar(c);
    printf("\n");
}

结果如下:

注意:

在 printf 中,所有的“非输出控制符”都要原样输出。而在 scanf 中,所有的“非输入控制符”都要原样输入。如下面的结果图所示,在输入的时候,“a=”和“b=”必须要原样输入按代码所示,%d和%d之间也需输入一个空格,且不能使用逗号或其他符号代替,否则报错

另外,在 scanf 函数中%d后面也没有必要加\n,因为在 scanf 中\n不起换行的作用;在 printf 函数中 \n 能起到换行的作用,否则就会将输出体现在同一行里。


四、指针

指针是指存储单元的地址,例如定义一个变量int a = 1;指向变量a的指针就是a在内存中的地址

1. 变量的指针和指针变量

(1)变量的指针

变量的指针:就是变量的内存地址

(2)指针变量

  • 指针变量的定义类型说明符* 变量名

指针变量:存放变量地址的变量。(例如:有个变量a,变量a的地址是p,p就是变量a的指针。现在我们再假设一个变量b,然后把p赋值给变量b,那么变量b就是一个指针变量。)

int* a; //定义一个int类型的指针变量,指向的变量类型也必须是int
char* b; //定义一个char类型的指针变量,指向的变量类型也必须是char
double* c; //定义一个double类型的指针变量,指向的变量类型也必须是double
  • 指针的操作

1)&:取地址运算符

&变量名表示取变量的地址(指针)

int a = 123;
int* p = &a; //取变量a的地址赋值给指针变量p

2) *:指针运算符

*指针变量表示取指向的变量的

int a = 123;
int* p = &a; //取变量a的地址赋值给指针变量p
printf("%d",*p); //输出123,*p表示取a的值

2. 数组的指针和指针数组

(1)数组的指针

一个数组是由连续的一块内存单元组成的。数组的指针是指数组的这块连续内存单元的首地址,即数组中第一个元素的地址

int array[] = {1,2,3,4,5,6};
int* pA = array; //数组名就是数组的指针
int* pB = &array[0]; //数组的第一个元素的地址就是数组的指针
//指针pA和指针pB是相等的

(2)指针数组

一个数组的元素值为指针则是指针数组。

定义方式:类型说明符* 数组名[数组长度](跟普通数组定义方式相同,唯一区别是*)

int main(){
   int a=1,b=2,c=3,d=4,e=5;
   int* Int[5] = {&a,&b,&c,&d,&e}; // 这是一个整型指针数组
   // 字符串在C语言中是字符数组,所以一个字符串相当于一个字符数组,字符串本身就等于字符数组的指针(首地址)
   const char* String[] = {"Test1","Test2","Test3","Test4","Test5"}; // 这是一个字符型的指针数组
   for (int i = 0; i < 5; ++i){
       printf("%p\n",String[i]); // 这里输出的就是每个字符串的指针
   }
   return 0;

3. 字符串的指针

  • C语言中是没有字符串类型的,C语言中的字符串都是用字符数组进行存储
  • 字符串的指针就是字符数组的指针,也就是字符数组的首地址

C语言字符串的两种定义形式:

  • 数组形式:char string[] = {'H','e','l','l','o','\0'}; 或 char string[] = "Hello";
  • 指针形式:char* string = "Hello";(等价于{'H','e','l','l','o','\0'})

4. 函数的指针&指针型函数

(1)函数的指针

在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;
} 

(2)指针型函数

函数类型(即函数返回值的类型)是指针的函数就是指针型函数

指针型函数的定义:

类型说明符* 函数名(参数){
   执行语句;
   return 对应类型的指针;
}

例如:下面定义了指针型函数,作用是随机生成一个数组,返回数组的指针。

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

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]);
    }

5. 指向指针的指针

指向指针的指针,根据字面意思举个例子:假如有个变量a,变量a的指针用p1表示,将p1赋值给一个变量b,变量b的指针用p2表示,现在将p2赋值给一个变量c,变量c就是指向指针的指针。

int a = 2333;
int* b = &a;
int** c = &b;

要访问指向指针的指针的值,要使用**。如上面的指针c,访问方式为**c。


五、结构体和公用体

1. 结构体

结构体是由多个不同数据类型的成员组成的数据类型。结构体中的每个成员可以有不同的数据类型和命名。使用结构体可以多个不同数据类型的信息组合成一个单一的逻辑单元(实现面向对象编程),从而方便地进行操作

(1)结构体的定义

  • 定义结构体关键字struct
  • 定义形式struct 结构名{成员数据};
//下面定义了一个名为Person的结构体
//Person包含有一个人的姓名、年龄、性别、身高、住址信息
struct Person{
    char* name;
    int age;
    char sex;
    double height;
    char address[200];
};

(2)结构体的用法

  • 结构体成员变量表示方法结构名.变量名(*结构指针).变量名/(*结构指针)->变量名
  • 结构体变量的赋值直接给成员变量赋值,注意数组类型不能直接赋值
#include <stdio.h>
#include <string.h>

// 下面定义了一个名为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("姓名:%s\n年龄:%d\n性别:%c\n身高:%.2fm\n地址:%s\n",woman.name,woman.age,woman.sex,(*pW).height,pW->address);

    return 0;
}

2. 共用体

共用体是一种特殊的结构体,其所有成员共享相同的内存空间。共用体中的每个成员可以有不同的数据类型,但是它们共享相同的内存空间,因此只能同时存在一个成员的值

共用体的主要用途不同的数据类型之间进行类型转换节省内存空间

(1)共用体的定义

  • 定义结构体关键字union
  • 定义形式union 共用体名{成员数据};

(2)共用体的用法

#include <stdio.h>
#include <string.h>

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");

    return 0;
}

通过上面的例子,如果把temp看做一个没有定义类型的变量,那么他就是个可变类型的变量。


六、动态内存分配

C语言常用的内存管理函数有四个:malloccallocreallocfree。其中申请空间的函数是malloccalloc;重新调整空间大小的函数是realloc释放空间的函数是free

malloc() 函数为例

malloc 只开辟空间(开辟的是用户所需求的字节数大小的空间),且不进行类型检查,只是在使用的时候进行类型的强制转换(malloc() 函数返回的实际是一个无类型指针,必须在其前面加上指针类型强制转换才可以使用)。

malloc() 函数使用形式: 指针自身 = (指针类型 *)malloc(sizeof(指针类型) * 数据数量)

例如:分配一块空间存储指定个数的数字,并对数字求和

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

int main() {
    int* ptr; //定义一个指针变量
    int n, sum = 0; //初始化元素个数与元素总和

    printf("输入要保存的元素个数: ");
    scanf("%d", &n);

    ptr = (int*) malloc(n * sizeof(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;
}

附录1:C语言常见头文件


C语言中常用的头文件包括但不限于以下几个:

  1. <stdio.h>提供了输入输出函数(Sandard Input and Output Header,标准输入输出头文件),如printfscanffopen等。
  2. <stdlib.h>提供了常用的实用函数(Standard Library Header,标准库头文件),如内存分配函数mallocfree, 随机数生成函数rand等。
  3. <string.h>提供了字符串处理函数,如strlenstrcpystrcat等。
  4. <math.h>提供了数学函数库,如三角函数sincossqrt等。
  5. <time.h>提供了与时间有关的函数,如time,用于获取当前时间。
  6. <ctype.h>提供了字符处理函数,如tolowertoupper, 测试字符类别的函数isalpha等。
  7. <stdbool.h>提供了布尔类型booltruefalse
  8. <limits.h>提供了有关实现常量的信息,如INT_MAXINT_MIN等。

使用这些头文件通常只需在程序的开始部分加上相应的包含指令即可,例如:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include <ctype.h>
#include <stdbool.h>
#include <limits.h>


附录2:文件操作

文件是一个有序数据集,数据集的名称叫文件名。文件分为两种,一种是普通文件,比如txt文件、C语言的源程序文件、头文件等等存在于磁盘上的;另一种是设备文件,比如鼠标、键盘、显示器等等外部设备,都认为是一个文件。

1. 文件指针

C语言使用一个指针变量指向一个文件通过操作指针来操作文件

文件指针的定义:FILE *变量名;(FILE 实际上是系统定义的一个结构体,该结构体中含有文件名、文件状态、文件当前位置等信息)

注意:

文件指针和文件位置指针需要区别开:文件指针指向的是整个文件,文件位置指针指的是文件中所处位置的指针(头部、当前位置、末尾等)。

2. 操作文件的函数

(1)打开与关闭

ANSIC 规定用 fopen 来打开文件,用 fclose 来关闭文件:

  • fopen:打开一个文件,成功则返回文件的指针,失败则返回空指针NULL
  • fclose:关闭一个文件,成功则返回0,失败则返回非0

函数原型如下:

FILE * fopen ( const char * filename, const char * mode );	//打开文件,fopen("文件名", "打开模式")
int fclose ( FILE * stream );	//关闭文件
  • path:文件路径。
  • mode:打开的模式。mode主要由以下6个字符组合而成:
    r:可读(文件位置指针在文件头部,文件必须存在)
    w:可写(文件位置指针在文件头部,文件存在则清空内容,不存在就创建)
    a:追加写入(文件位置指针在文件尾部,文件必须存在)
    b:二进制方式打开
    +:可读写
    t:文本模式(默认,可省略)

  • mode的常用模式:

    选项说明
    r只读打开一个文本文件,只允许读数据
    w只写打开一个文本文件,只允许写数据
    a追加写入打开一个文本文件,在文件末尾写数据
    rb以二进制方式打开一个文件,只允许读数据
    wb以二进制方式打开一个文件,只允许写数据

(2)文件读写

文件结束符:EOF

文件写入的函数需要以或者读写模式打开文件,文件读取的函数需要以或者读取的模式打开文件。读取或写入操作之后,位置指针都会向后移动到读取或写入位置的末尾

函数名功能适用性
fgetc()字符输入函数所有输入流
fputc()字符输出函数所有输出流
fgets()文本行输入函数所有输入流
fputs()文本行输出函数所有输出流
fscanf()格式化输入函数所有输入流
fprintf()格式化输出函数所有输出流
fread()二进制输入文件
fwrite()二进制输出文件

1)fgetc:从文件读取一个字符

  • 函数原型:int fgetc(FILE *file);
    • file:目标文件的指针
  • 返回值:返回 int 类型的 ASCII 码,位置指针向后移动一个字节
  • 使用方法:fgetc(文件指针);

2)fputc:向文件中写入一个字符

  • 函数原型:int fputc(int c, FILE *file);
    • c:要写入的字符(char 或者 int 类型 ASCII 码)
    • file:目标文件的指针
  • 返回值:成功返回写入的字符,位置指针向后移动一个字节;失败返回 EOF
  • 使用方法:fputc('a', 文件指针);

3)fgets:从文件读取一个字符串到字符数组中

  • 函数原型:char* fgets(char *Buffer, int MaxCount, FILE *file );
    • Buffer:字符数组的指针
    • MaxCount:最大读取字符数
    • file:目标文件的指针
  • 说明:
    • MaxCount是一个正整数,表示从文件中读出的字符串不超过 MaxCount-1个字符。在读入的最后一个字符后加上串结束标志 \0
    • 在读出MaxCount-1个字符之前,如遇到了换行符或EOF,则读出结束。
  • 返回值:字符数组的首地址
  • 使用方法:fgets(数组首地址, 字符串最大长度, 文件指针);

4)fputs:将一个字符串写入到文件中,不包含’\0’

  • 函数原型:int fputs(const char *str, FILE *file);
    • str:要写入的字符数组(字符串)的指针
    • file:目标文件的指针
  • 返回值:成功返回非负整数;失败返回 EOF(符号常量,其值为-1)
  • 使用方法:fputs(字符串, 文件指针);

5)fread:从文件中读取一组固定大小的数据到内存空间

  • 函数原型:size_t fread(void *Buffer, size_t size, size_t count, FILE *file);
    • Buffer:内存空间首地址(用来存放数据的内存空间指针)
    • size:数据块的大小
    • count:数据块的数量
    • file:目标文件的指针
  • 返回值:返回成功读取的对象个数(若出现错误或到达文件末尾,则可能小于count)
  • 使用方法:fread(内存空间地址, 数据块大小, 数据块数量, 文件指针);

6)fwrite:写入一组固定大小的数据到文件中

  • 函数原型:size_t fwrite(const void *Buffer, size_t size, size_t count, FILE *file);
    • Buffer:要存入的数据的首地址
    • size:数据块的大小
    • count:数据块的数量
    • file:目标文件的指针
  • 返回值:返回成功写入的对象个数(若出现错误或到达文件末尾,则可能小于 count)
  • 使用方法:fwrite(数据地址, 数据块大小, 数据块数量, 文件指针);

7)fscanf:从文件中获取指定格式的数据,跟scanf类似,输入对象换成了普通文件

  • 函数原型:int fscanf(FILE *file, const char *str, [arg...]);
    • file:目标文件的指针
    • str:格式化字符串
    • [arg…]:一个或多个接收数据的地址
  • 说明:fscanf 遇到空格和换行时结束
  • 返回值:成功返回读入参数的个数,失败返回 EOF
  • 使用方法:fscanf(文件指针, 格式化字符串, 目标地址);

8)fprintf:格式化输出数据到文件,跟printf类似,输出对象换成了普通文件

  • 函数原型:int fprint(FILE *file, const char *str, [arg...]);
    • file:目标文件的指针
    • str:格式化字符串
    • [arg…]:一个或多个数据
  • 说明:fprintf 会根据参数 str 字符串来转换并格式化数据,然后将结果输出到参数file指定的文件中,直到出现字符串结束(\0)为止。
  • 返回值:成功返回输出数据的个数,失败返回 EOF
  • 使用方法:fprintf(文件指针, 格式化字符串, 目标数据);

(3)文件操作实例

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

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)); // 看看位置指针是不是在结束位置

    fclose(fp); //关闭文件

    return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/577785.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【运维】Git 分支管理

一般来讲&#xff0c;系统代码需要经过研发、测试、生产三种环境。那么在Git上如何管理分支&#xff0c;才不会乱&#xff1f;在线上生产环境有问题时有条不紊的解决。 经过发展&#xff0c;有一个Git Flow原理可帮助解决。设置以下几种分支。 master——production生产环境。…

Fusion360导入STL和OBJ文件转化为实体文件自由编辑

Fusion360导入STL和OBJ文件转化为实体文件自由编辑 1.概述 在模型网站上下载的3D打印文件通常是STL和OBJ格式文件&#xff0c;该类型文件都是网格类型的文件&#xff0c;Fusion360只可以对实体文件进行编辑。因此不能对他们直接修改&#xff0c;需要导入文件将他们转为实体文…

Linux多进程(五) 进程池 C++实现

一、进程池的概念 1.1、什么是进程池 进程池是一种并发编程模式&#xff0c;用于管理和重用多个处理任务的进程。它通常用于需要频繁创建和销毁进程的情况&#xff0c;以避免因此产生的开销。 进程池的优点包括&#xff1a; 减少进程创建销毁的开销&#xff1a;避免频繁创建和…

笔记:编写程序,分别采用面向对象和 pyplot 快捷函数的方式绘制正弦曲线 和余弦曲线。 提示:使用 sin()或 cos()函数生成正弦值或余弦值。

文章目录 前言一、面向对象和 pyplot 快捷函数的方式是什么&#xff1f;二、编写代码面向对象的方法&#xff1a;使用 pyplot 快捷函数的方法&#xff1a; 总结 前言 本文将探讨如何使用编程语言编写程序&#xff0c;通过两种不同的方法绘制正弦曲线和余弦曲线。我们将分别采用…

备考2024年小学生古诗文大会:做做10道历年真题和知识点(持续)

根据往年的安排&#xff0c;2024年上海市小学生古诗文大会预计还有一个月就将启动。我们继续来随机看10道往年的上海小学生古诗文大会真题&#xff0c;这些题目来自我去重、合并后的1700在线题库&#xff0c;每道题我都提供了参考答案和独家解析。 根据往期的经验&#xff0c;只…

【网络原理】TCP协议的相关机制(确认应答、超时重传)

系列文章目录 【网络通信基础】网络中的常见基本概念 【网络编程】Java网络编程中的基本概念及实现UDP、TCP客户端服务器程序&#xff08;万字博文&#xff09; 【网络原理】UDP协议的报文结构 及 校验和字段的错误检测机制&#xff08;CRC算法、MD5算法&#xff09; 文章目…

uniapp制作分页查询功能

效果 代码 标签中 <uni-pagination change"pageChanged" :current"pageIndex" :pageSize"pageSize" :total"pageTotle" class"pagination" /> data中 pageIndex: 1, //分页器页码 pageSize: 10, //分页器每页显示…

第72天:漏洞发现-Web框架中间件联动GobyAfrogXrayAwvsVulmap

案例一&#xff1a;某 APP-Web 扫描-常规&联动-Burp&Awvs&Xray Acunetix 一款商业的 Web 漏洞扫描程序&#xff0c;它可以检查 Web 应用程序中的漏洞&#xff0c;如 SQL 注入、跨站脚本攻击、身份验证页上的弱口令长度等。它拥有一个操作方便的图形用户界 面&#…

基于yolov5实时实例分割

是一个结合了最新技术进展&#xff08;State-of-the-Art, SOTA&#xff09;的实时实例分割项目&#xff0c;基于著名的YOLOv5目标检测架构&#xff0c;并对其进行扩展以实现对图像中每个对象实例的精确像素级分割。以下是该项目的中文介绍&#xff1a; YOLOv5&#xff1a; YOL…

数据结构八:线性表之循环队列的设计

上篇博客&#xff0c;学习了栈&#xff0c;我们可以知道他也是一种线性表&#xff0c;遵从先进后出的原则&#xff0c;在本节&#xff0c;我们进一步学习另一种线性表—队列。就像饭堂里排队打饭的的队伍&#xff0c;作为一种先进先出的线性表&#xff0c;他又有哪些特别之处呢…

实现Spring底层机制(二)

文章目录 阶段2—封装bean定义信息到Map1.代码框架图2.代码实现1.文件目录2.新增注解Scope存储单例或多例信息Scope.java3.修改MonsterService.java指定多例注解4.新增bean定义对象存储bean定义信息BeanDefinition.java5.修改pom.xml增加依赖6.修改容器实现bean定义信息扫描Sun…

C语言|关于C语言变量的作用域、链接、存储期及相关知识(C多文件编程基础)

文章目录 作用域块作用域(block scope)函数作用域(function scope)函数原型作用域(function prototype scope)文件作用域(file scope)翻译单元和文件(作用域&#xff09; 链接(linkage)存储期(Storege Duration)静态存储期(static storage duration)线程存储期(thread storage …

kafka启动报错(kafka.common.InconsistentClusterIdException)

文章目录 前言kafka启动报错(kafka.common.InconsistentClusterIdException)1. 查找日志2. 定位问题/解决 前言 如果您觉得有用的话&#xff0c;记得给博主点个赞&#xff0c;评论&#xff0c;收藏一键三连啊&#xff0c;写作不易啊^ _ ^。   而且听说点赞的人每天的运气都不…

qt实现方框调整

效果 在四周调整 代码 #ifndef MAINWINDOW_H #define MAINWINDOW_H#include <QWidget>class MainWindow : public QWidget {Q_OBJECT public:explicit MainWindow(QWidget *parent 0);~MainWindow();void paintEvent(QPaintEvent *event);void updateRect();void re…

ZYNQ--PL读写PS端DDR数据

PL 和PS的高效交互是zynq 7000 soc开发的重中之重,我们常常需要将PL端的大量数 据实时送到PS端处理,或者将PS端处理结果实时送到PL端处理,常规我们会想到使用DMA 的方式来进行,但是各种协议非常麻烦,灵活性也比较差,本节课程讲解如何直接通过AXI总 线来读写PS端ddr的数据…

什么是基尼系数

基尼系数是国际上用来综合考察居民内部收入分配差异状况的一个重要分析指标。每个人的收入有多有少&#xff0c;差距大时&#xff0c;基尼系数就高&#xff1b;差距小时&#xff0c;基尼系数就低。 一、基本概念 基尼系数表示在全部居民收入中&#xff0c;用于进行不平均分配…

补充centos7软件包的方式/编译安装源码包软件/企业案例/linux进程管理/企业管理进程系列命令(企业经验)--8820字详谈

cenros7软件包的安装方式 软件包分类安装方式优缺点rpm包软件开发商编译打包&#xff0c;安装简单&#xff0c;快速软件版本可能偏低&#xff0c;安装路径是固定好的源码包自己手动编译安装并且复杂软件爸爸随意选&#xff0c;可以定制安装路径二进制包解压就可以使用不能进行…

什么是AIGC技术

AIGC技术&#xff0c;即人工智能全局优化控制技术&#xff0c;是一种将人工智能与全局优化控制方法相结合的技术。它的主要目标是通过智能化的方法来解决复杂系统的优化问题&#xff0c;提高系统的性能和效率。 AIGC技术的主要目标是利用人工智能算法和技术来实现对系统整体的…

第三篇:Python编程基础:掌握核心语法与开发技巧

Python编程基础&#xff1a;掌握核心语法与开发技巧 1 引言 在这个信息化迅速蔓延的世界中&#xff0c;Python语言如同钥匙一般开启了通往各种可能性的大门。无论你是数据科学家、网络工程师、机器学习专家&#xff0c;还是仅仅对自动化办公感兴趣的办公室人员&#xff0c;Pyt…

【Elasticsearch<二>✈️✈️】基本属性概念与MySQL数据库的不同之处

目录 &#x1f378;前言 &#x1f37b;一、Elasticsearch 基本属性 1.1 ES VS MySQL 1.2 ES 属性概念 1.3 ES 的增删改查 &#x1f37a;二、自动补全场景 2.1 场景举例 2.2 使用数据分词器 2.3 查询的流程 2.4 整个查询流程图 &#x1f379;章末 &#x1f378;前言 上次初步…