C语言入门——第六课

本文详细解释了typedef用于类型别名、static修饰局部变量和函数、extern声明外部变量、const常量特性,以及它们在结构体中的应用。还讨论了宏替换与typedef的区别,以及全局变量和局部变量的初始化和可见性问题。
摘要由CSDN通过智能技术生成

目录

1.概念

2.示例

 3.typedef和结构体

别名可以是结构体名

 4.宏替换和typedef的区别

二、static关键字

1.局部变量

2.全局变量

3.函数

4.静态变量初始化

​编辑5. 静态变量的可见性

三、extern关键字

1.概念

四、const关键字

1.概念

2.const和类型结合

3.const和*

4.const和其变量指针

5.const常变量外部引用

五、Q&A

1.全局变量和局部变量未初始化

2. 全局变量被称为数据


一、typedef关键字

1.概念

typedef是在计算机编程语言中用来为复杂的声明定义简单的别名。它本身是一种存储类的关键字,与auto、extern、mutable、static、register等关键字不能出现在一个表达式中。

typedef unsigned char unit_8;

int main()
{
	unit_8 str = 'a';
	printf("%c", str);
}

这里的unit_8 str = 'a'等于char str = 'a',unit_8是char类型的别名。

2.示例

 typedef可以把一切合法的变量定义转换成类型重命名。

示例如下:

首先定义一些合法的变量,如下

int INT;
int* PINT;
char Array[10];
void (*PFUN)(void);
double DOU;

加上typedef关键字,变成类型的重命名。

typedef int INT;//整型类型的别名叫做INT
typedef int* PINT;//整型指针别名叫做PINT
typedef char Array[10];//char类型别名叫做Array
typedef void (*PFUN)(void);//返回值和形参都为空的函数指针别名叫做PFUN
typedef double DOU;//double类型别名为DOU

上述代码作用为:

整型类型的别名叫做INT
整型指针别名叫做PINT
char类型别名叫做Array
返回值和形参都为空的函数指针别名叫做PFUN
double类型别名为DOU

确定到底是什么类型,就是将typedef去掉,剩下的是什么类型就是什么类型。

直接使用别名定义对应类型的变量。

void func()
{
	printf("func\n");
}
typedef int INT;//整型类型的别名叫做INT
typedef int* PINT;//整型指针别名叫做PINT
typedef char Array[10];//char类型的数组别名叫做Array
typedef void (*PFUN)(void);//返回值和形参都为空的函数指针别名叫做PFUN
typedef double DOU;//double类型别名为DOU

int main()
{
	INT a = 10;
	PINT p =  &a;
	Array ar, br;
	PFUN fun = func;
	DOU d = 10.0;
}

 3.typedef和结构体

结构体变量可以通过关键字typedef有别名。

写法一

struct SqList 
{
	int size;
	int* data;
};
typedef struct SqList mylist;
int main()
{
	mylist youlist;
}

结构体重命名的另外一种写法,写法二如下:

typedef struct SqList 
{
	int size;
	int* data;
}SqList;

int main()
{
	 SqList mylist;
}

我们一般采取第二种写法,方便,简单。 

别名可以是结构体名

通常情况下,我们变量名和关键字不能重复,但是,结构体是个例外,它的变量名可以和结构体名一致,但是不能和结构体类型名一致。

struct Student
{
    int age;
    char[20] name;
    char[20] id;
}

在这个结构体中,Student叫做结构体名,struct Student叫做结构体类型名。

struct SqList 
{
	int size;
	int* data;
};
typedef struct SqList mylist;
int main()
{
	mylist SqList;
}

在对结构体通过关键字typedef修饰找重命名时,重命名的名称可以和结构体名一致。

struct SqList 
{
	int size;
	int* data;
};
typedef struct SqList SqList;
int main()
{
	

在严蔚敏的《数据结构》一书中,有下面的代码 

代码的含义是通过关键字typedef给一个不具名的结构体起一个别名叫做SqList。 

 4.宏替换和typedef的区别

#define PINT int*
typedef int* SINT;
int main()
{
	int a = 10;
	PINT ip= &a;//int * ip = &a;
	SINT sp= &a;
}

define的核心是替换 ,第二行代码在编译时会直接将PINT替换成int*。上述代码并看不出来define和typedef的区别,因为它们都是对a取地址。

在下面代码中宏替换define和typedef关键字有所差异:

#define PINT int*
typedef int* SINT;
int main()
{
	int a = 10,b = 20;
	PINT ip, sp;//int* ip,int sp;
	SINT ap, bp;//int* ap,int*bp;
}

PINT ip, sp,实际上定义了一个整形指针int* ip,一个整型变量int sp。产生这样现象的原因是在进行编译的过程中,PINT直接被换成了int*,但是*一般是和标识符(变量)结合,所以它和ip结合*ip,后面sp的类型就只是整型了。

SINT ap,bp则是定义了两个整型指针ap、bp,上述情况这就是两者的差异,只修饰一个变量两者没有区别,如果一次性同时修饰多个变量是有很大的差异的,需要注意。

二、static关键字

可修饰

①局部变量

②全局变量

③函数(函数和全局变量一样,是全局概念)

1.局部变量

①静态局部变量在函数第一次调用时初始化,第二次调用不会初始化;

②一般的局部变量会调用时开辟空间,初始化,然后函数结束就销毁,但是静态局部变量在函数结束时并不消失,它被存放在.data区;

③一般的局部变量不初始化,分配的初始值为随机值,但是如果不给静态局部变量初始化值,它的默认值是0。

void func()
{
	int a = 10;
	static int b = 10;
	a += 1;
	b += 1;
	printf("a=%d b=%d\n", a, b);
}
int main()
{
	func();
	func();
}

输出结果如下: 

 请自己计算下面代码输出的结果:

void func(int x)
{
	int a = x;
	static int b = x;
	a += 1;
	b += 1;
	printf("a=%d b=%d\n", a, b);
}
int main()
{
	for (int i = 5; i > 0; i--)
	{
		func(i);
	}
}

根据static的性质,它的结果如下:

2.全局变量

 全局变量的static的作用是,令static所修饰的全局变量只在本文件可见,在其他文件不可见。extern和static不能联合使用。

如果在A.cpp有static int max =10;

在B.cpp也命名了int max;

此时,这二者命名并不冲突,因为在B中看不到A中的变量。

3.函数

static修饰函数时,和全局变量一样,在其他文件中看不到该函数。需要注意的是,它是修饰函数的,并不是修饰返回值,更不是修饰形参的。

4.静态变量初始化

Q:计算机怎么知道静态变量已经被初始化了呢?

A:在静态变量地址的下面或者上面开辟了一个空间叫做标志域,用于存储标识符,标志域存储0或1,表示静态变量是否被初始化了。

 如果将静态变量的标志域一直修改为未初始化状态,程序会怎么执行。

void func(int x)
{
	int a = x;
	static int b = x;
	int* p = &b;
	p += 1;
	*p = 0;
	a += 1;
	b += 1;
	printf("a=%d b=%d\n", a, b);
}
int main()
{
	for (int i = 5; i > 0; i--)
	{
		func(i);
	}
}

通过调试窗口可以看到,b被初始化的标志符为1,未初始化标识符为0。我们先取b的地址,b地址的下一个是它的标志域,我们修改这一区域的标志符,使其一直为0(未初始状态),结果如下: 

5. 静态变量的可见性

下面这种书写方式是错误的,可见性是在编译链接过程中的,此时的a只在函数中可见,只有它执行之后才存放在.data区域,才能全局可用。但是编译链接未通过没法谈执行成功,所以,在这里主函数不能调用a,因为它此时只是一个局部变量。

三、extern关键字

 一个工程只能有一个主函数。两个主函数的话,会链接出错。 

PS:C语言和C++中不能存在函数嵌套,函数是最小执行单元。

下面的例子中,想要在B.cpp调用A.cpp定义的函数和变量,编译链接过程出错,B.cpp报错,表示找不到对应的函数和变量。

1.概念

extern外部关键字告诉程序我要声明一个变量,但这个变量不在当前源文件中,在其他源文件中。加入extern外部关键字后,可以保证当前文件编译通过形成obj文件。在链接阶段,与工程其他文件的obj文件链接时才调用外部变量的地址空间。(链接:将名称和地址产生关联)

在其他文件调用另一个文件的函数,可以省略书写extern外部关键字。

下面的例子,通过extern关键字,将B.cpp的max变量声明,使B.cpp可以成功编译成obj文件,两个obj文件链接时才能获取max的地址信息。
 

注意:当然不能在两个源文件中定义相同的变量,命名重复喽!

test.cpp

test_1.cpp

 运行程序,它会报错,显示重复定义了变量a。

四、const关键字

1.概念

将变量转变成常变量。当用const修饰变量时 ,修饰的变量只读不可写;

凡是const修饰的变量,无论局部还是全局变量,必须初始化;

因为不初始化的话,局部变量就是随机值,随机值只读不写没意义;

为了全局变量和局部变量规则保持一致,所以无论全局变量还是局部变量都必须初始化。

2.const和类型结合

eg:const int a,b =0;

a,b都要初始化,因为都被const修饰

3.const和*

const在指针左右修饰的东西不一样,如果const在指针的右边,修饰的是标识符,如果const在指针的左边,修饰的是类型,也就是所指之物的值。

4.const和其变量指针

const修饰的变量,它的指针只能加const修饰。

避免在C语言的编译规则下指针的值被修改,从而使得常变量不安全。

5.const常变量外部引用

const关键字修饰的全局变量只在本文件可见,想要在外部文件可见,需要使用extern关键字。

现象

在A.cpp定义常变量

在B.cpp加外部引用关键字extern,会显示编译错误,b变量未定义。

修改方法

在A.cpp常变量const前加上extern。

错误产生的原因

const在使用的时候会直接将变量进行替换,所以在外部引用的时候没有办法获取其地址。

补充

extern和static不可同时使用,作用冲突。static的作用是外部不可见,但是extern的作用是将变量在外部引用。

五、Q&A

1.全局变量和局部变量未初始化

Q:全局变量和局部变量未初始化,内存存储的值是什么?

A:如果定义了全局变量但是未初始化,程序将会自动为它赋值为0。 

如果局部变量未被初始化,程序会给他赋一个随机值,一般赋的值是十六进制的c,我们常见的“烫”,就是16进制0xcc表示的汉字。示例如下:

2. 全局变量被称为数据

Q:为什么全局变量叫数据?

A:全局变量在计算机编程中通常被称为"全局数据"或"全局数据变量",这是因为它们在整个程序中都是可见和可访问的,即它们的作用范围在全局范围内。这些变量不仅可以在程序的主函数内访问,还可以在程序的其他函数内访问,包括自定义函数、库函数等。

全局变量通常在程序的全局作用域内声明,因此它们的作用范围跨越了整个源代码文件。全局变量的特点包括:

  1. 全局可见性:全局变量可以在程序的任何地方访问,无需传递给函数或特定作用域。

  2. 生命周期:全局变量的生命周期通常与程序的生命周期相同。它们在程序启动时创建,在程序结束时销毁。

  3. 持久性:全局变量的值在函数调用之间保持不变,因此它们可以用于存储持久性数据,如配置设置或状态信息。

  4. 易于滥用:因为全局变量可以被程序中的任何部分访问,过度使用全局变量可能导致代码不易维护和调试。因此,良好的编程实践通常建议将变量的作用范围限制在尽可能小的范围内,以减少不必要的依赖关系。

总之,全局变量之所以称为"全局数据",是因为它们在程序中扮演了全局范围内可访问的数据存储的角色,但也需要谨慎使用,以确保代码的可维护性和健壮性。

推荐书籍——《程序员的自我修养》 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值