初识C语言第四次笔记

C语言第四次笔记

一:常用关键字

1:typedef

(1):作用:

用typedef来给类型取新的名字,简化了复杂类型

#include<stdio.h>
//typedef
typedef unsigned int uint;//用typedef来给类型取新的名字,简化了复杂类型
int main()
{
    
    
	uint num = 0;
	uint num1 = 1;
	return 0;
}

(2):在结构体中的应用:

可以对结构体使用 typedef 来定义一个新的数据类型名字,然后使用这个新的数据类型来直接定义结构变量

#include<stdio.h>
typedef struct student
{
    
    
	char ID[16];
	char name[16];
	char sex;
	float score;
}STUDENT;

(3):typedef与define的区别

typedef 仅限于为类型定义符号名称,#define 不仅可以为类型定义别名,也能为数值定义别名,比如您可以定义 1 为 ONE。
typedef 是由编译器执行解释的,#define 语句是由预编译器进行处理的。

2:static

(1):修饰局部变量

在此之前我们先来学一下void:

void 在英文中作为名词的解释为 “空虚、空间、空隙”,而在 C 语言中,void 被翻译为"无类型"
void 的作用当函数不需要返回值值时,必须使用void限定

#include<stdio.h>
//void 在英文中作为名词的解释为 "空虚、空间、空隙",而在 C 语言中,void 被翻译为"无类型"
//void 的作用当函数不需要返回值值时,必须使用void限定,这就是我们所说的第一种情况。例如:void //func(int a,char *b)。
//当函数不允许接受参数时,必须使用void限定,这就是我们所说的第二种情况。例如:int func(void)。
void test()
{
    
    
	int a = 1;
	a++;
	printf("%d ",a);
}
int main()
{
    
    
	int i = 0;
	while (i < 10)
	{
    
    
		test();//i<10进入到test()中,然后前往void test(),打印出a,因为void无返回值,所以局部变量的生命周期被自动销毁
		i++;
	}
	return 0;
}

由于void无返回值,局部变量被自动销毁​​​​
​​​​​​​​​​在这里插入图片描述
加了static之后:

#include<stdio.h>
//void 在英文中作为名词的解释为 "空虚、空间、空隙",而在 C 语言中,void 被翻译为"无类型"
//void 的作用当函数不需要返回值值时,必须使用void限定,这就是我们所说的第一种情况。例如:void func(int a,char *b)。
//当函数不允许接受参数时,必须使用void限定,这就是我们所说的第二种情况。例如:int func(void)。
void test()
{
    
    
	static int a = 1;//static修饰后的a并没有被销毁,而是继续进入主函数,第八行的代码使用完后就没意义了
	a++;
	printf("%d ",a);
}
int main()
{
    
    
	int i = 0;
	while (i < 10)
	{
    
    
		test();//i<10进入到test()中,然后前往void test(),打印出a
		i++;
	}

	return 0;
}

在这里插入图片描述
普通的局部变量是放在内存的栈区上的,进入局部范围,变量创建,出了局部范围变量销毁

static修饰局部变量的时候,局部变量在静态区开辟空间,局部变量出了作用域,并不销毁。本质上,static修饰局部变量的时候,改变了变量的存储位置,影响了变量的生命周期,使得生命周期变长,和程序生命周期一样,变量存储静态区,变为静态变量。(改变了存储位置,由栈区->静态区,使得变量的生命周期发生了变化。)

1.1:下面我们来区分一下栈区 堆区和静态区.

静态区(全局区):静态变量和全局变量的存储区域是一起的,一旦静态区的内存被分配, 静态区的内存直到程序全部结束之后才会被释放。

堆区:由程序员调用malloc()函数来主动申请的,需使用free()函数来释放内存,若申请了堆区内存,之后忘记释放内存,很容易造成内存泄漏。

栈区:存放函数内的局部变量,形参和函数返回值。栈区之中的数据的作用范围过了之后,系统就会回收自动管理栈区的内存(分配内存 , 回收内存),不需要开发人员来手动管理。
栈区就像是一家客栈,里面有很多房间,客人来了之后自动分配房间,房间里的客人可以变动,是一种动态的数据变动。

在这里插入图片描述

(2):修饰全局变量:

利用extern代码将两个不同的文件连接到一起去

test4.10.1.c

static int a = 10;

test4.10.2.c

#include<stdio.h>
extern int a;
int main()
{
    
    
    printf("%d ",a);
    return 0;
}
//err

此程序报错 a无定义,但是把static去掉,这个代码则是正确的

这是为什么呢?

答:static修饰全局变量的时候,这个全局变量的外部链接属性变成了内部链接属性,其他源文件(.c)就无法再使用这个全局变量了,使用的时候生命周期变短了

(3):修饰函数

同理可知static修饰函数的时候,这个函数本身的外部链接属性变成了内部链接属性,其他源文件(.c)就无法再使用这个全局变量了,使用的时候生命周期变短了.

那么什么是链接呢?

不管我们编写的代码有多么简单,都必须经过「编译 --> 链接」的过程才能生成可执行文件:

编译就是将我们编写的源代码“翻译”成计算机可以识别的二进制格式,它们以目标文件的形式存在;

链接就是一个“打包”的过程,它将所有的目标文件以及系统组件组合成一个可执行文件。

3:register

电脑常用的储存设备:寄存器、高速缓存、内存、硬盘

这些设备具有如下关系:

寄存器(集成到CPU上) 越往上读取速度越快
高速缓存 空间越小,造价越高
内存 越往下读取速度越慢
硬盘 空间越大,造价越低

在这里插入图片描述

在计算机的发展中中,最早期的时候只存在内存的概念,数据主要存放在内存中,运行程序的时候CPU(中央处理器)必须去内存中读取数据来运行程序。
然而随着计算机的发展,CPU处理数据的效率越来越高,然而此时内存的读写速度已经跟不上CPU的处理速度了,这时就出现了高速缓存和寄存器,它们的读写速度更快,可以适应CPU的使用。
就好像垒墙,一个人叠砖头,一个人递砖头,那个垒墙的人速度越来越快,然而那个递砖的却没有加快速度。此时,垒墙的人只能干瞪眼等着砖头,大大降低了工作效率。
然而读写速度越快,设备的造价越高,可容纳的数据容量越小。计算机就选择了这样的运行方式:
数据有限存储在寄存器中,然后存储到高速缓存中,然后储存在内存中,在寄存器中的数据使用完成后,高速缓存的数据会跟进到寄存器中,内存的数据也会跟进到高速缓存中,在这样的数据处理下,大部分的数据都可以在寄存器中获得,这样就跟上了CPU的处理速度。

register后的变量表示建议放到寄存器中,只是建议,真正的放置需要电脑自身的分配。不过现在我们的计算机已经十分先进了,你不写register它也知道哪个该放进寄存器中。

#include<stdio.h>
int main()
{
    
    
//寄存器变量
    register int num = 3;//3放到寄存器中
    return 0;
}

4:#define定义常量和宏

(1):定义常量

#include<stdio.h>
#define NUM 100//此处NUM表示一个常量值为100,为了区别,这个常量名固定为大写
int main()
{
    
    
    printf("%d\n", NUM);
	int n = NUM;
	printf("%d\n", n);
	int arr[NUM] = {
    
     0 };
	return 0;
}

在这里插入图片描述

(2):定义宏

#include<stdio.h>
#define ADD(x,y) ((x)+(y))//ADD为宏名,x,y为宏参数,x+y为宏体。
int Add(int x, int y)//注意是Add非ADD
{
    
    
	return x + y;
}
int main()
{
    
    
	int a = 10;
	int b = 20;
	int c = ADD(a, b);//int c =((a)+(b)),将a,b替换到x,y中
	printf("%d\n",c);
	return 0;
}

在这里插入图片描述

二:指针

1:内存

程序的运行需要内存,我们为了有效地使用内存,就需要将内存划分为一个个小的内存单元,每一个单元的大小是一个字节。(一个字节比较合理,这个内存单元太小也不好,太大也不好)为了 能够有效地使用每个内存单元,我们给每一个单元都定了一个编号,这个编号就叫做这个内存单元的地址。

就像在我们的生活中,比如说你在网上购物。你就一定需要告诉商家,你自己的确切位置,比如说xx市xx区xx路xx号(几号楼)几号宿舍。)这个内存的地址也是这个道理,就像楼中的门牌号,通过编号的方式,内存的单元地址也就确定了。我们可以轻松地找到对应的地址,而不需要一个一个去找。

在这里插入图片描述

2:地址的生成

我们的电脑中都有硬件电路,用于生成地址的电线叫地址线。当电路中有电路通过时,会产生正负脉冲,从而表示0与1.此处我们以32位电脑为例,它在生成地址时32根地址线同时产生电信号表示1或0,当每一个地址线组合起来时就有了许许多多的不同的排列组合方式。

00000000000000000000000000000000——对应0

00000000000000000000000000000001——对应1

1111111111111111111111111111111111111——最终可能会变成32个1

这样的排序方式一共有2^32次方种 内存中一共有这么多byte的空间。
(1024B=1KB 1024KB=1MB 1024MB=1GB) (1byte=8bit)
但是这个数字不是很直观,我们先对它除以1024得到4194304个KB,再除1024得到4096个MB,再除以1024得到4GB,也就是说在早期的三十二位电脑内存中一共有4GB的内存空间。

3:数据的储存

变量是创建内存中的(在内存分配空间的),每个单元都有地址,所以变量也有地址
利用&:
&:取地址操作符,取出谁的地址。
打印地址,%p是以地址的形式打印

#include <stdio.h>
int main()
{
    
    
    int a = 10;//向内申请4个字节,存储10
    &a;//取出a的地址,&为取地址符号
    //这里的a共有4个字节,每个字节都有地址,但我们取出的是第一个字节的地址(较小的地址)
    printf("%p\n", &a);//打印地址,%p是以地址的形式打印
    return 0;
}

​​打印出来后 通过调试F10 内存 监视窗口得到此图
在这里插入图片描述

我们实际上取出的只有0x010FF808这个地址(起始位置的地址)

0a 00 00 00一行显示了四个字节 (设置了四列)

a的值为10,用二进制表示即为:0000 0000 0000 0000 0000 0000 0000 1010(二进制的数字表达最后一位表示2的0次方,倒数第二位就表示2的1次方,以此类推,十就是2的3次方加2的一次方也就是1010),在这个时候我们以每个四位为一组,就可以得到数据的表示方法:00 00 00 0a(在16进制数中,a表示10,b表示11,c表示12,d表示13,e表示14,f表示15)

** 0000 0000 0000 0000 0000 0000 0000 1010
0 0 0 0 0 0 0 a**

其实十进制的储存方式是这样的
0x 00 00 00 0a 倒着存

4:指针变量

#include<stdio.h>
int main()
{
    
    
    int a = 10;
    int* p = &a;
 //我们把a这个变量的地址储存在这个变量p中,这个p就叫做指针变量,类型为int*   
 //变量p是创建出来存放地址(指针)的。   
    return 0;
}

在内存单元中 编号就是地址 而地址就是指针
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

对int* p =&a;的理解

1.p代表0x010FF808这个起始位置的地址
2.中间的表示p是个指针变量,注意指针变量是p,而不是p
3.int说明p指向的对象是int类型的(本例子说明p指向的是a)
4.p为指针变量,接受&a的内容,(即应该将地址存到指针变量中去)也就是变量的首地址

5:解引用操作符*

*(解引用操作符/间接操作符):通过地址找到地址所指向的内容。
*p表示对指针变量p解引用(p就是p所指的对象),通过p中存放的地址找到p所指的对象(对应的内容)

#include <stdio.h>
int main()
{
    
    
    int a = 10;//向内申请4个字节,存储10
    &a;//取出a的地址,&为取地址符号
    //这里的a共有4个字节,每个字节都有地址,但我们取出的是第一个字节的地址(较小的地址)
    //printf("%p\n", &a);//打印地址,%p是以地址的形式打印
    int* p = &a;//p指向了a 接受了&a,即接受了a的内容
    //*p通过p中存放的地址,找到p指向的空间(对象/变量)
    *p = 20;//p指向了a,等价于a=20
    printf("%d\n",a);   
    return 0;
}

在这里插入图片描述

6:指针变量的大小

%zu表示打印sizeof

#include <stdio.h>
int main()
{
    
    
        printf("%zu\n", sizeof(char*));//zu表示打印sizeof
        printf("%zu\n", sizeof(short*));
        printf("%zu\n", sizeof(int*));
        printf("%zu\n",sizeof(float*));
        printf("%zu\n", sizeof(double*));
        return 0;
}

你可能认为输出结果是:1 2 4 4 8

但实际上是:4\8 4\8 4\8 4\8 4\8 (4或8)

因为:
指针变量储存的是地址,也就是说指针变量的大小取决于存放一个地址需要多大的空间,32位平台下地址是32个bit位(即4个字节),而64位平台下地址是64个bit位(即8个字节),所以指针变量的大小就是4或8.

结论:
32位环境下,地址的序列就由32个1/0组成的二进制序列,要存储进来,需要4个字节。
64位环境下,地址的序列就由64个1/0组成的二进制序列,要存储进来,需要8个字节。

7:如何一口气定义好几个指针变量?

int main()
{
    
    
    int* p1,p2,p3 = &a;
    //这个定义方式是不正确的,只有p1是指针变量而其他两个是整型变量
    int* p1,*p2,*p3;
    //这个定义方法才是正确的,在第一种方法下,*只给第一个变量使用
    return 0;
}

三:结构体

结构体(struct)使C语言有能力描述复杂的类型。其能把单一类型组合在一起

比如描述一个学生,一个学生包含: 名字+年龄+性别+学号。

我们用基本的数据类型没有办法描述这样的复杂对象,这里就只能使用结构体来描述了。

struct Stu//struct表示创建结构体,Stu表示这个数据类型叫struct Stu
{
    
    
	char name[20];
	int age;
	char sex[10];
	char tele[12];
};
//通过结构体将这些简单数据类型集合来描述一个复杂的对象
struct Stu s = {
    
     "zhangsan",18,"male","2022000415" };
//定义一个struct Stu类型的结构体变量s

做个类比 我们盖房子需要图纸
在这里插入图片描述
上述代码就是图纸
struct Stu s类似于房子 s这个时候在内存有一个独立的空间存在

#include<stdio.h>
struct Stu//struct表示创建结构体,Stu表示这个数据类型叫struct Stu
{
    
    
	char name[20];
	int age;
	char sex[10];
	char tele[12];
};
int main()
{
    
    
	//通过结构体将这些简单数据类型集合来描述一个复杂的对象
	struct Stu s = {
    
     "zhangsan", 18, "male", "2022000415" };
	//定义一个struct Stu类型的结构体变量s
	printf("%s %d %s %s\n", s.name, s.age, s.sex, s.tele);//s.代表从结构体中摘取过来的数据,切记数据之间不可替换顺序
	return 0;
}

在这里插入图片描述
**s.代表从结构体中摘取过来的数据,切记数据之间不可替换顺序,否则编译器会崩溃,它是有序性的

.操作符 在结构体中的用法——//结构体对象.内部变量名
还有其他的打印方法:

#include<stdio.h>
struct Stu//struct表示创建结构体,Stu表示这个数据类型叫struct Stu
{
    
    
	char name[20];
	int age;
	char sex[10];
	char tele[12];
};
void print(struct Stu* ps)//将地址存到指针变量中去
{
    
    
	printf("%s %d %s %s\n", (*ps).name, (*ps).age, (*ps).sex, (*ps).tele);//ps里面存了s的地址
    //(*ps)得到了结构体变量s,这个.表示在结构体内部找到变量
	//.的用法:结构体对象+内部变量名
	//(*ps).name:将ps解引用找到结构体,再找到name变量
	printf("%s %d %s %s\n", ps->name, ps->age, ps->sex, ps->tele);
	//ps->就表示在指针指向的结构体中找到对应的变量
	//->的用法:结构体指针变量+内部变量名
	//ps->name:找到指针变量指向的结构体内的name变量
	//一般用第二种打印方法 第一种太麻烦了

}
int main()
{
    
    
	//通过结构体将这些简单数据类型集合来描述一个复杂的对象
	struct Stu s1 = {
    
     "zhangsan", 18, "male", "2022000415" };
	struct Stu s2 = {
    
    "lisi", 20, "male", "202200618"};
	//定义一个struct Stu类型的结构体变量s
	//输入数据到s2中
	scanf("%s %d %s %s\n", s2.name, &s2.age, s.sex, s.tele);//结构体数组中不需要输入&(取地址操作符)
	//结构体对象.内部变量名(.操作符在结构体中的用法)
	printf("%s %d %s %s\n", s.name, s.age, s.sex, s.tele);//s.代表从结构体中摘取过来的数据,切记数据之间不可替换顺序
	print(&s2);//print函数,把s2地址取出来给函数
	return 0;
}

猜你喜欢

转载自blog.csdn.net/yanghuagai2311/article/details/125641723
今日推荐