最名不符实的关键字 - static
先来介绍一下多文件
main.c程序源码
#define _CRT_SECURE_NO_WARNINGS 1 //#include<stdio.h> //extern int g_val;//声明 //extern int g_val = 10;//这样写可以吗? - error /* 在我们没有加入关键字extern的时候,编译器提示 “g_val”是未声明的标识符,所以我们就需要声明该变量,并不需要 开辟空间,而我们上面的第二中写法是变量的定义并进行初始化,需要开辟空间 而我们前面以及在test.c文件中已经开辟过了,而现在再次开辟会多次定义变量。 这是编译器也提示出了错误:error LNK2005: g_val 已经在 NO2.obj 中定义 fatal error LNK1169: 找到一个或多个多重定义的符号 结论:所有的变量再在声明的时候不可以设置初始值 */ //int main() //{ //show(); //调用test.c的show函数,在本程序可以使用吗? //可以 - 只有警告并为报错 //warning C4013: “show”未定义;假设外部返回 int //printf("%d", g_val); //使用test.c的全局变量g_val,在本程序可以使用吗? //不可以 // error C2065: “g_val”: 未声明的标识符 //怎么处理呢? - extern关键字 //printf("%d", g_val);//这样就可以用运行了 // return 0; //}
test.c源码
//#include<stdio.h>这里我们也不需要写这句代码啦,在test.h中都包含啦! #include "test.h" int g_val = 10; void show() { printf("hello world!\n"); }
单纯的使用源文件,组织项目结构的时候,项目越大越复杂,维护的成本也会越来越高,因为我们写的函数或者全局边量会多次被不同的源文件调用而每次调用的时候我们就要声明,这样一个项目将会很复杂,这时我们就需要引用.h结尾的头文件。
头文件在组织项目结构的时候,减少大型项目的维护成本问题。.h基本上都是要被多个源文件包含,但这时可能会有一个问题:头文件会被重复包含
test.h源码
/* 单纯的使用源文件,组织项目结构的时候, 项目越大越复杂,维护的成本也会越来越高, 因为我们写的函数或者全局边量会多次被不同的源文件调用 而每次调用的时候我们就要声明,这样一个项目将会很复杂 这是我们就需要引用.h结尾的头文件 头文件在组织项目结构的时候,减少大型项目的维护成本问题 .h基本上都是要被多个源文件包含,但这时可能会有一个问题 头文件会被重复包含 */ #pragma once //解决头文件被重复包含 /* 1.C头文件 2.所有的变量的声明 3.所有的函数的声明 4.#deifne,类型typedef,结构体struct */ #include<stdio.h> #include<windows.h> extern int g_val;//必须带上,虽然不报错,但是你写成 //int g_val;你就不清楚这是声明还是定义了 extern void show();//可以不带extern,但建议带,养成好习惯 这时有人说了,为什么变量必须需要,而函数只是建议呢? 因为函数的声明和定义是看函数是否有函数体 void show();有;结束了,没有函数体,这个就一定声明
当我们再main.c的程序中引用test.h头文件时,这时g_val变量和show()函数的声明都已经在test.h声明了,我们不需要再声明就可以是用了,而且没有任何警告和错误。
修改后的main.c源码
#include"test.h" int main() { printf("%d\n", g_val); show(); //这时g_val变量和show()函数的声明都已经在test.h声明了, //我们不需要再声明就可以是用了 return 0; }
运行结果:
通过上面的多文件的学习我们可以知道:
全局变量和函数都可以跨文件访问,但是在未来我们编写程序时,我们不想要全局变量和函数都可以跨文件访问,只想在本文件内部被访问,这是我们就需要使用static关键字。
stati修饰全局变量和函数
当我们再test.c程序中的int g_val = 10;和void show()前加上static修饰时
#include "test.h" static int g_val = 10; static void show() { printf("hello world!\n"); }
再运行main.c程序时就产生链接性(LNK)报错,这样我们就达到了不想要全局变量和函数都可以跨文件访问,只想在本文件内部被访问的目的了。
static修饰局部变量
#include <stdio.h>
void foo() {
int count = 0;
count++;
printf("This is the %dth time I'm called.\n", count);
}
int main() {
foo();
foo();
foo();
return 0;
}
在上述代码中,我们定义了一个名为 `foo` 的函数,并在其中定义了一个局部变量 `count`。每次调用 `foo` 函数时,`count` 的值都会自增,并且在函数内部打印出当前函数被调用的次数。由于 `count` 是局部变量,它会在函数结束释放空间,导致count内容也没有了,因此以后每次调用 `foo` 函数时都不会记住上一次的值,每次进入函数都会重现创建变量count,为其开辟空间进行初始化。因此输出的结果会类似于:
This is the 1th time I'm called.
This is the 1th time I'm called.
This is the 1th time I'm called.
当我们用static修饰局部变量count时
#include <stdio.h>
void foo() {
static int count = 0;
count++;
printf("This is the %dth time I'm called.\n", count);
}
int main() {
foo();
foo();
foo();
return 0;
}
每次调用 `foo` 函数时,`count` 的值都会自增,并且在函数内部打印出当前函数被调用的次数。由于 `count` 是静态变量,它只会在程序初次运行时被初始化,以后每次调用 `foo` 函数时都会记住上一次的值,因此输出的结果会类似于:
This is the 1th time I'm called.
This is the 2th time I'm called.
This is the 3th time I'm called.
我们再来看一段代码:
#include<stdio.h>
int* p = NULL;
void fun()
{
static int b = 100;
p = &b;
}
int main()
{
fun();
printf("%d\n", *p);//100
return 0;
}
我们可以发现,当我们进行程序调试的时候,当调试到printf函数的时候,我们发现b和&b已经是未声明的标识符了,说明此时变量已经出了它的作用域,从而可以证明static修饰的局部变量不会改变变量的作用域。再回到上面那个程序,调用`foo` 函数,虽然count变量每次出了作用域也不能访问它,但是编译器却记住了每一次count的值,再其作用域内打印count的值只有第一次进行初始化,后面一次是count++的值,但是当我们去掉static的时候,每次调用函数都会为count变量进行初始化,并没有记录count的值,说明static修饰的局部变量增长了局部变量的生命周期。
static关键字总结
1. 全局变量:使用 `static` 关键字修饰变量时,它的作用域被限定在声明该变量的源文件内,不会被其他源文件所直接使用。此外,静态变量的生命周期和程序的执行周期相同,它只会在程序初次运行时被初始化,以后每次函数调用都会记住上一次的值,不会被销毁。
2. 静态函数: 使用 `static` 关键字定义函数时,该函数的作用域被限定在声明该函数的源文件内部,其它源文件无法直接访问该函数。这在一些小的项目中很有用,可以减少命名冲突和全局污染的问题。
3. 局部变量:使用 static 修饰的局部变量块会在程序启动时只被初始化一次,之后每次进入该代码块都会记住上一次执行的结果。
总的来说,`static` 关键字可以用于限制变量、函数的作用域和可见性,保证其只在需要的范围内被访问和使用。同时,静态变量的生命周期和作用域是静态的,能够方便地保存一些信息,减少全局变量带来的问题。
注意上面加粗的语言,不能直接访问,但是可以间接访问,我们可以使用嵌套的方式对外进行调用,所有并不是绝对的不能访问。这样的间接访问,使用者只会知道其实现的功能,并不知道实现方式,极大程度上维护了程序的安全,是一种程序的封装。这样别人无法修改我们的全局变量和函数,这样我们的程序就非常安全。
static项目维护,提供安全保证
#include "test.h" static int g_val = 10; static void show() { printf("%d\n", g_val); printf("hello world!\n"); } void show1() { show(); //当我们在test.c中声明该函数并且main.c中调用该函数就可以使用全局变量和函数了 }
如果我们也不想此函数被其他文件再使用,那咱们就加static。
几个问题
1.c语言要设置全局变量和函数可以跨文件使用的原因
C语言要设置全局变量和函数可以跨文件使用,主要是为了方便程序员进行代码组织、管理和重用。具体原因如下:
1. 代码组织和管理方便:将不同功能的代码模块分别编写在不同的文件中,可以使得代码的组织和管理更加清晰和方便,便于程序员维护和管理代码。
2. 函数重用性提高:将函数定义在一个独立的文件中,可以使得程序员在不同的程序中多次调用相同的函数,从而提高了函数的重用性,避免了重复编写代码的麻烦。
3. 变量共享:全局变量可以在程序的不同部分之间共享,可以在函数之间传递数据,避免了在每个函数之间频繁地传递数据的麻烦。
4. 代码可读性提高:将不同功能的代码模块分别编写在不同的文件中,也有助于代码的可读性,使得程序员可以更加清晰地理解程序的结构和逻辑。
总之,C语言的全局变量和函数可以跨文件使用,为程序员提供了方便的代码组织、管理和重用方式,有助于提高程序的可维护性、可重用性和可读性。多个文件之间的函数调用可以通过函数声明和头文件实现,使得程序员可以将不同功能的代码模块分别编写在不同的文件中,方便代码的管理和维护。同时,这种跨文件的函数调用方式也提高了代码的可重用性,可以使得相同的函数可以在不同的程序中多次调用,降低了代码的重复编写和维护成本。
2.C程序地址空间是什么样的?
C程序的地址空间通常被划分为以下五个部分:
1. 文本段(Text Segment,也称代码段/只读代码区):存放程序的机器指令,是可执行的代码段。该段通常是只读的,并且是共享的,因为多个进程可以同时运行同一个程序,而它们所使用的指令流是相同的。在程序运行期间,文本段的内容通常是不变的。这个区域一般包括程序的指令、常量、字符串等数据。
2. 数据段(Data Segment,也称已初始化数据区/全局数据区):存放程序中已经初始化的全局变量和静态变量,例如int、float、double等类型的数据。这个区域的数据在程序的整个执行过程中都存在,并且可以被程序中的所有函数所访问。
3. BSS段(或未初始化数据段):存放程序中未初始化的全局变量和静态变量,例如未初始化的int、float、double等类型的数据。在程序运行之前,BSS段的内容被初始化为0或NULL。随着程序的执行过程,程序可以动态地在BSS段中分配内存。
4. 堆(Heap):存放程序运行期间动态分配的内存。堆的大小可以根据程序的需要进行扩展或缩小,不能保证堆的连续性。通常,程序员通过调用malloc、calloc、realloc等函数来使用堆内存。
5. 栈(Stack):存放函数调用时的局部变量、函数的参数、以及函数的调用和返回信息,这些数据的大小在编译时不能确定。栈的大小可以根据需要动态地增加或减少,但是栈内存的使用必须保证栈的连续性。
3.局部变量为什么具有临时性
局部变量具有临时性是因为它们只在函数调用期间存在,当函数返回时,局部变量的内存空间就会被释放。这是因为局部变量在栈上分配内存,栈是一种具有后进先出(LIFO)特性的内存结构,每次函数调用时,系统会在栈上分配一段内存作为函数的临时存储空间,在函数返回时,系统会释放这段内存,以便让其他函数使用。
因此,局部变量的生命周期受限于函数的执行周期,当函数执行结束时,它所占用的内存空间都被释放了,局部变量也就不存在了。这种临时性使得局部变量只能在函数内部使用,对于其他函数来说是不可见的。
相比之下,全局变量和静态变量是具有永久性的,它们在程序启动时被分配内存,并在整个程序执行期间存在。因此,全局变量和静态变量可以在程序的任何位置进行访问和修改,而局部变量只能在函数内部使用。
总之,局部变量的临时性是由于它们只在函数调用期间存在。
4.全局变量为什么具有全局性
全局变量具有全局性是因为它们在程序运行期间始终存在于内存中,并且可以在程序的任何地方被访问到。这是因为全局变量的存储位置是在程序的静态数据区或全局数据区中。这个区域在程序开始时就被分配出来,直到程序结束时才会被释放掉。
在 C 语言中,全局变量的定义通常放在函数外部,这会导致它们的作用域扩展到整个源代码文件。如果全局变量在某个文件中定义,但没有被声明为 static,那么该变量的作用域将扩展到整个程序中,即使在其他文件中也可以通过 extern 关键字来访问它,这就是所谓的“全局性”。
全局变量在编译时就已经被分配了内存空间,因此它们的地址在程序运行期间是固定的。这些全局变量存储在静态数据区或全局数据区,这个区域通常不可写,因此被称为“常量区”或“只读数据区”。全局变量的地址在程序整个运行期间都是固定的,可以在程序的任何地方被访问和修改。
总之,全局变量具有全局性是因为它们存储在地址空间的静态数据区或全局数据区中,并且在程序整个运行期间都存在于内存中。
5.为什么static修饰的局部变量生命周期会变长
在 C中,static 关键字可以用来改变变量的存储方式、作用域和生命周期。当 static 用来修饰局部变量时,它的作用是使得这个变量的生命周期变长,即在函数执行完之后,该变量的值仍然存在于内存中,没有被释放掉,直到程序结束时才会释放掉这个变量所占用的内存空间。这种特性使得 static 修饰的局部变量可以在函数调用之间共享信息,对于一些需要保持状态的变量,比如计数器、缓存等,可以使用 static 修饰。
在函数第一次被调用时,静态局部变量才被创建,之后每次调用函数时,它的值保持不变。因此,静态局部变量在内存中只能被创建一次,并且只被初始化一次,以后每次函数调用时,静态局部变量所占用的内存地址是相同的。这就是为什么 static 修饰的局部变量的生命周期会比普通局部变量的生命周期更长的原因。
需要注意的是,static 修饰的变量只能在定义它们的函数内部访问,对于其他函数来说是不可见的。另外,由于静态局部变量在内存中只被创建一次,所以它们的初始值只被赋值一次,如果没有显式的初始化语句,它们会被自动初始化为 0 或者 NULL。
6.static修饰的局部变量再地址空间会有什么变化吗
static 修饰的局部变量在地址空间上与普通局部变量存在差别。
普通局部变量通常存在于栈空间中,栈空间是一种后进先出的数据结构。每次函数调用时,系统会在栈空间中为该函数分配一段临时的内存空间,函数执行结束时,这段内存空间就会被释放。
而 static 修饰的局部变量则存在于全局数据区中,也称为静态数据区、静态存储区或者数据段。全局数据区是在程序运行期间一直存在的一块内存空间,由编译器在程序启动时自动分配,它的大小在程序运行期间不会发生改变。在程序运行期间,该内存空间中的值始终保持不变。因此,static 修饰的局部变量在地址空间上与普通局部变量存在差别。具体而言,在 x86 架构的计算机中,全局数据区会被分成两个部分:
1. 数据段(Data Segment):用于存储全局变量、静态变量以及常量字符串等。数据段的大小在编译时就已经确定。
2. BSS 段(Block Started by Symbol):用于存储未经初始化的全局变量、静态变量以及 BSS 段数组。BSS 段的大小在程序启动时会被设置为 0,并在程序运行时动态分配内存。
static 修饰的局部变量在存储上与全局变量类似,它们都存储在数据段或 BSS 段中,具有静态存储周期和全局作用域。而对于普通局部变量,在函数调用结束后,它们所占用的栈空间就会被释放掉,对应的地址空间也就不存在了。
基本数据类型
前面我们已经介绍了定义变量的本质是:在内存中开辟一块空间,用来保存数据。而定义一个变量,是需要类型的,这个是基本语法决定的,那么,类型决定了:变量开辟空间的大小。
最冤枉的关键字 - sizeof
sizeof 是 C中的一个关键字,用于获取数据类型或变量在内存中所占用的字节数。sizeof 操作符返回值的类型是 size_t,这是一个无符号整数类型,大小足以容纳所查询对象的大小。
sizeof 操作符可以用于以下情况:
1. 获取数据类型的大小:使用 sizeof 可以查询数据类型的大小,例如 sizeof(int) 可以获取 int 类型在当前平台上所占用的字节数。
2. 获取变量的大小:使用 sizeof 可以查询变量在内存中所占用的字节数,例如 sizeof(a) 可以获取变量 a 在内存中所占用的字节数。
注意,sizeof 可以用于任何数据类型,包括基本数据类型和自定义数据类型,例如结构体和类。 在使用 sizeof 时,需要注意以下几点:
1. sizeof 返回值的类型是 size_t,需要在代码中使用正确的类型进行接收和处理。
2. 对于指针类型,sizeof 返回的是指针变量本身所占用的字节数,而不是指针所指向的对象所占用的字节数。如果要获取指针所指向的对象大小,可以使用 sizeof(*ptr),其中 ptr 是指向对象的指针。
3. 在使用 sizeof 获取数组大小时,需要注意数组中每个元素的大小,因为数组所占用的总字节数等于元素大小乘以数组长度。
综上所述,sizeof 是一个用于获取数据类型或变量在内存中所占用的字节数的关键字,可以帮助程序员更好地管理内存和设计数据结构。在使用时需要注意数据类型、指针、数组等特殊情况。
sizeof关键字是求数据类型的大小,单位是字节
#include <stdio.h>
int main()
{
printf("%d\n", sizeof(char)); //1字节
printf("%d\n", sizeof(short)); //2字节
printf("%d\n", sizeof(int)); //4字节
printf("%d\n", sizeof(long)); //4字节
printf("%d\n", sizeof(long long)); //8字节
printf("%d\n", sizeof(float)); //4字节
printf("%d\n", sizeof(double)); //8字节
printf("%d\n", sizeof(long double)); //8字节
return 0;
}
接下来,我们来看一段代码
#include<stdio.h>
int main()
{
//sizeof:确定一种类型对应在开辟空间时候的大小
//判断那个代码能输出正确的结果
int a = 10;
printf("%d\n", sizeof(int));
printf("%d\n", sizeof(a));
printf("%d\n", sizeof int);
printf("%d\n", sizeof a);
return 0;
}
这段代码主要演示了C语言中`sizeof`关键字的使用。 `sizeof`可用于计算数据类型、变量、数组等的大小。在这段代码中,`sizeof(int)`、`sizeof(a)`和`sizeof a`分别求`int`类型和`a`变量所占的字节数,输出结果相同。 而`sizeof int`,在实际运行时会报错。因为`sizeof`后面必须要跟着一个表达式,而`int`不是完整的表达式。最终,这段代码会输出以下结果:
4
4
error: expected expression(应输入表达式)
4
一般我们写函数调用的时候,例如入求两数之和,当我们要使用的时候,就调用函数,形式‘Add(a,b)’,这个和上面的sizeof(a)很相似,但是我们千万不能把sizeof也当作函数,它只是个操作符,printf("%d\n", sizeof a);上面的这句代码也可以运行,没有带小括号(),而我们知道函数调用需要使用小括号()操作符,所以我们可以证明sizeof只是操作符,不是函数。