参考:菜鸟教程:https://www.runoob.com/
局部变量和全局变量(C 作用域规则)
- 任何一种编程中,作用域是程序中定义的变量所存在的区域,超过该区域变量就不能被访问。C 语言中有三个地方可以声明变量:
- 在函数或块内部的局部变量。
- 在所有函数外部的全局变量。
- 在形式参数的函数参数定义中。
让我们来看看什么是局部变量、全局变量和形式参数。
局部变量
实例:
#include <stdio.h>
int main(int argc, char const *argv[])
{
//局部变量声明
int a,b,c;
//局部变量的初始化
a = 10;
b = 20;
c = a + b;
printf("c is %d. \n",c);
printf("Hello World!\n");
return 0;
}
全局变量
实例:
#include <stdio.h>
//全局变量声明
int c;
int main(int argc, char const *argv[])
{
//局部变量声明
int a,b;
//局部变量的初始化
a = 10;
b = 20;
//全局变量初始化
c = a + b;
printf("c is %d. \n",c);
printf("Hello World!\n");
return 0;
}
在程序中,局部变量和全局变量的名称可以相同,但是在函数内,如果两个名字相同,会使用局部变量值,全局变量不会被使用。下面是一个实例:
#include <stdio.h>
/* 全局变量声明 */
int g = 20;
int main ()
{
/* 局部变量声明 */
int g = 10;
printf ("value of g = %d\n", g);// g的值是10
return 0;
}
形式参数
实例:
#include <stdio.h>
/* 全局变量声明 */
int a = 20;
int main ()
{
/* 在主函数中的局部变量声明 */
int a = 10;
int b = 20;
int c = 0;
int sum(int, int);
printf ("value of a in main() = %d\n", a);
c = sum( a, b);
printf ("value of c in main() = %d\n", c);
return 0;
}
/* 添加两个整数的函数 */
int sum(int a, int b)
{
printf ("value of a in sum() = %d\n", a);
printf ("value of b in sum() = %d\n", b);
return a + b;
}
输出为:
value of a in main() = 10
value of a in sum() = 10
value of b in sum() = 20
value of c in main() = 30
结构体内存对齐原则(重点)
结构体对齐规则
1、规则1:
结构体的成员变量的起始地址(偏移量),是该成员变量的数据类型的大小(字节byte
)的整数倍。
如int是4个字节,那就只能存放在偏移量为0,4,8,…开始的内存地址。结构体的第一个变量的偏移量为0。
或者说,结构体成员的内部偏移量(内部地址)要被这个成员的数据类型的大小整除。 即 偏移量/sizeof(数据类型)。2、规则2:
整个结构体的大小,必须是其内部的最大成员变量的数据类型的大小的整数倍。
如最大成员变量为double
类型,那么整个结构体的大小必须是8的整数倍。3、规则3:
对于结构体内部嵌套的结构体,一般按照结构体展开之后的内存对齐来处理。4、规则4:
人为指定特定的对齐规则!
使用#pragma pack(n) 注意:n必须为1或者2的整数倍。
即 地址起始的偏移量为n的整数倍,结构体的大小也为n的整数倍。
指定每个成员的起始地址,按照n来对齐,覆盖规则1和规则2。5、 注意:
如果这个规定的对齐数n比结构体内部的所有的成员变量的数据类型都要大,则就取两者中的小的。 是对第一条规则的补充。
规则1:
规则1: 结构体的成员变量的起始地址(偏移量),是该成员变量的数据类型的大小(字节byte)的整数倍。
如int是4个字节,那就只能存放在偏移量为0,4,8,…开始的内存地址。结构体的第一个变量的偏移量为0。
或者说,结构体成员的内部偏移量(内部地址)要被这个成员的数据类型的大小整除。 即 偏移量/sizeof(数据类型)。
实例:
// [] 代表一个字节byte。
struct test_001
{
char c1; //1 + 3*[]
int c2; //4
char c3; //1 + 3*[]
};// 4 + 4 + 4 = 12 byte
如上结构体所示,由于规则1
的对齐原则,int
类型的起始地址的偏移量为4
,再由于规则2
结构体的大小规则,即该结构体的大小为最大成员变量的数据类型的大小的整数倍,即int(4 byte)
的整数倍,故最终大小为12 byte
。
再看一个实例:
struct test_002
{
char c1; //1
char c3; //1 + 2*[]
int c2; //4
};// 1 + 3 + 4 = 8 byte
规则2:
规则2: 整个结构体的大小,必须是其内部的最大成员变量的数据类型的大小的整数倍。
实例:
struct test_003
{
double a; //8
int b; //4 + 4*[]
};// 8 + 8 = 16 byte
该结构体的大小为double (8 byte)
的整数倍,故最终大小为16 byte
。
规则3:
规则3:对于结构体内部嵌套的结构体,一般按照结构体展开之后的内存对齐来处理。
实例:
struct test_003
{
double a; //8
int b; //4 + 4*[]
};// 8 + 8 = 16 byte
struct test_004
{
char c1; //1 + 3*[]
int c2; //4
char c3; //由于下面的结构体的第一个成员变量为double类型,根据规则1,所以大小为 1 + 7*[]
struct test_003 test3; // 8 + 8 = 16 byte
};// 4 + 4 + 8 + 16 = 32 byte
规则4:
规则4: 人为指定特定的对齐规则! 使用#pragma pack(n)
注意:n
必须为1
或者2
的整数倍。 指定每个成员的起始地址,按照n来对齐,覆盖规则1和规则2。
实例:
#pragma pack(1) // 规定偏移量为1.即 地址起始的偏移量为1的整数倍。结构体的大小也为1的整数倍。
struct test_003
{
double a; //8
int b; //4
};// 8 + 8 = 12 byte
struct test_004
{
char c1; //1
int c2; //4
char c3; //1
struct test_003 test3; // 8 + 4 = 12 byte
};// 1 + 4 + 1 + 12 = 18 byte
再看一个实例:
#pragma pack(2) // 规定偏移量为2.即 地址起始的偏移量为2的整数倍。结构体的大小也为2的整数倍。
struct test_003
{
double a; //8
int b; //4
};// 8 + 4 = 2 byte
struct test_004
{
char c1; //1 + 1*[]
int c2; //4
char c3; //1 + 1*[]
struct test_003 test3; // 8 + 4 = 12 byte
};// 2 + 4 + 2 + 12 = 20 byte
再看一个实例:
#pragma pack(4) // 规定偏移量为4. 即 地址起始的偏移量为4的整数倍。结构体的大小也为4的整数倍。
struct test_003
{
double a; //8
int b; //4
};// 8 + 4 = 12 byte
struct test_004
{
char c1; //1 + 3*[]
int c2; //4
char c3; //1 + 3*[]
struct test_003 test3; // 8 + 4 = 12 byte
};// 4 + 4 + 4 + 12 = 24 byte
注意:
注意:如果这个规定的对齐数n比结构体内部的所有的成员变量的数据类型都要大,则就取两者中的小的。 是对第一条规则的补充。
#pragma pack(16) // 规定偏移量为16. 由于16大于8,所以该偏移量无效,相当于8。
struct test_003
{
double a; //8
int b; //4 + 4*[]
};// 8 + 8 = 16 byte
struct test_004
{
char c1; //1 + 3*[]
int c2; //4
char c3; //由于下面的结构体的第一个成员变量为double类型,根据规则1,所以大小为 1 + 7*[]
struct test_003 test3; // 8 + 8 = 16 byte
};// 4 + 4 + 8 + 16 = 32 byte
C共用体
- 共用体是一种特殊的数据类型,允许您在相同的内存位置存储不同的数据类型。您可以定义一个带有多成员的共用体,但是任何时候只能有一个成员带有值。共用体提供了一种使用相同的内存位置的有效方式。
定义:
union [union tag]
{
member definition;
member definition;
...
member definition;
} [one or more union variables];
实例:
#include <stdio.h>
#include <string.h>
union Data
{
int a;
float b;
char str[20];
};
int main(int argc, char const *argv[])
{
union Data data;
data.a = 100;
data.b = 3.1415;
strcpy(data.str, "C Programming") ;
int size = sizeof(data);
printf("size of data is %d.\n",size);
printf("data.a is %d\n", data.a);
printf("data.b is %f\n", data.b);
printf("data.str is %s\n", data.str);
printf("Hello World!\n");
return 0;
}
输出为:
size of data is 20.
data.a is 1917853763
data.b is 4122360580327794900000000000000.000000
data.str is C Programming
Hello World!
可见,共用体内,只能同时使用一个成员变量,且共用体的大小为最大成员变量的大小。
#include <stdio.h>
#include <string.h>
union Data
{
int a;
float b;
char str[20];
};
int main(int argc, char const *argv[])
{
union Data data;
int size = sizeof(data);
printf("size of data is %d.\n",size);
data.a = 100;
printf("data.a is %d\n", data.a);
data.b = 3.1415926;
printf("data.b is %f\n", data.b);
strcpy(data.str, "C Programming");
printf("data.str is %s\n", data.str);
printf("Hello World!\n");
return 0;
}
输出:
size of data is 20.
data.a is 100
data.b is 3.141593
data.str is C Programming
Hello World!
静态变量
static
- (1)在修饰变量的时候,
static
修饰的静态局部变量只执行初始化一次,而且延长了局部变量的生命周期,直到程序运行结束以后才释放。- (2)
static
修饰全局变量的时候,这个全局变量只能在本文件中访问,不能在其它文件中访问,即便是
extern
外部声明也不可以。- (3)
static
修饰一个函数,则这个函数的只能在本文件中调用,不能被其他文件调用。static
修饰的变量存放在全局数据区的静态变量区,包括全局静态变量和局部静态变量,都在全局数据区分配内存。初始化的时候自动初始化为0
。- (4)不想被释放的时候,可以使用
static
修饰。比如修饰函数中存放在栈空间的数组。如果不想让这个数组在函数调用结束释放可以使用static
修饰。- (5)考虑到数据安全性(当程序想要使用全局变量的时候应该先考虑使用
static
)。
实例:
#include <stdio.h>
int main(int argc, char const *argv[])
{
for (int i = 0; i < 10; ++i)
{
static int a = 10;
a = a + 1;
printf("value of a is %d.\n", a);
}
printf("Hello world!\n");
return 0;
}
输出:
value of a is 11.
value of a is 12.
value of a is 13.
value of a is 14.
value of a is 15.
value of a is 16.
value of a is 17.
value of a is 18.
value of a is 19.
value of a is 20.
Hello world!
即static
声明的变量只有一次有效的初始化,并且其内存上的值是可以被修改的,直到程序重新运行后,在重新赋值。
const
const
声明一个只读变量,声明之后不允许改变。意味着,一旦声明必须初始化,否则会报错。
实例:
#include <stdio.h>
const int num = 10;
int main(int argc, char const *argv[])
{
printf("value of num is %d.\n", num);
printf("Hello world!\n");
return 0;
}
输出:
value of num is 10.
Hello world!
如果要修改num
的值,则会报错。