C/C++ 变量、结构体、数组存储本质

C语言中变量是程序可操作性的存储区域的名称,每个变量类型决定了变量的存储空间大小和布局。在C语言中,(1)变量是一段实际连续存储空间的别名;(2)程序中通过变量来申请并命名存储空间;(3)通过变量的名字可以使用存储空间。因此,我们学习变量,核心要关注的内容是其内存分部情况。变量内存使用情况一般编程中不需要考虑太多,在实际系统开发过程中大小端系统或者是数值计算过程中是需要被慎重考虑的。C 语言也允许定义各种其他类型的变量,比如枚举、指针、数组、结构、共用体等等,这将会在后续的章节中进行讲解。

类型 内存描述
char 通常是一个字节(八位), 这是一个整数类型。
int 整型,4 个字节,取值范围 -2147483648 到 2147483647。
float

单精度浮点值。单精度是这样的格式,1位符号,8位指数,23位小数。

double

双精度浮点值。双精度是1位符号,11位指数,52位小数。

C/C++ 变量、结构体、数组存储本质_第1张图片

void

表示类型的缺失。

* 指针类型变量用来指定特定类型的变量地址,大小32位计算机固定是4字节,64位固定式8字节
结构体
联合体

1. C 中的变量定义

        变量定义就是告诉编译器在何处创建变量的存储,以及如何创建变量的存储。变量定义指定一个数据类型,并包含了该类型的一个或多个变量的列表,如下所示:type variable_list;

        在这里,type 必须是一个有效的 C 数据类型,可以是 char、w_char、int、float、double 或任何用户自定义的对象,variable_list 可以由一个或多个标识符名称组成,多个标识符之间用逗号分隔。下面列出几个有效的声明:

int    i, j, k;
char   c, ch;
float  f, salary;
double d;

行 int i, j, k; 声明并定义了变量 i、j 和 k,这指示编译器创建类型为 int 的名为 i、j、k 的变量。

变量可以在声明的时候被初始化(指定一个初始值)。初始化器由一个等号,后跟一个常量表达式组成,如下所示:

type variable_name = value;

下面列举几个实例:

extern int d = 3, f = 5;    // d 和 f 的声明与初始化
int d = 3, f = 5;           // 定义并初始化 d 和 f
byte z = 22;                // 定义并初始化 z
char x = 'x';               // 变量 x 的值为 'x'

不带初始化的定义:带有静态存储持续时间的变量会被隐式初始化为 NULL(所有字节的值都是 0),其他所有变量的初始值是未定义的。

2. C 中的变量声明

变量声明想编译器保证变量以指定的类型和名称存在,这样编译器再不需要知道变量完整细节的情况下也能继续编译。变量声明只在编译时有它的意义,在程序连接时编译器需要实际的变量声明。C语言中变量的存储空间分为栈和堆两种方式,其中全局变量存储在指定的静态堆空间,局部变量在栈中生成。变量声明有两种情况:

  • 1、一种是需要建立存储空间的。例如:int a 在声明的时候就已经建立了存储空间。
  • 2、另一种是不需要建立存储空间的,通过使用extern关键字声明变量名而不定义它。 例如:extern int a 其中变量 a 可以在别的文件中定义的。
  • 除非有extern关键字,否则都是变量的定义。
extern int i; //声明,不是定义
int i; //声明,也是定义

我们知道,程序的编译单位是源程序文件,一个源文件可以包含一个或若干个函数。在函数内定义的变量是局部变量,而在函数之外定义的变量则称为外部变量,外部变量也就是我们所讲的全局变量。它的存储方式为静态存储,其生存周期为整个程序的生存周期。全局变量可以为本文件中的其他函数所共用,它的有效范围为从定义变量的位置开始到本源文件结束。
然而,如果全局变量不在文件的开头定义,有效的作用范围将只限于其定义处到文件结束。如果在定义点之前的函数想引用该全局变量,则应该在引用之前用关键字 extern 对该变量作“外部变量声明”,表示该变量是一个已经定义的外部变量。有了此声明,就可以从“声明”处起,合法地使用该外部变量。

3. C中左值和右值

C 中有两种类型的表达式:

  1. 左值(lvalue):指向内存位置的表达式被称为左值(lvalue)表达式。左值可以出现在赋值号的左边或右边。
  2. 右值(rvalue):术语右值(rvalue)指的是存储在内存中某些地址的数值。右值是不能对其进行赋值的表达式,也就是说,右值可以出现在赋值号的右边,但不能出现在赋值号的左边。

变量是左值,因此可以出现在赋值号的左边。数值型的字面值是右值,因此不能被赋值,不能出现在赋值号的左边。下面是一个有效的语句:

int g = 20;

但是下面这个就不是一个有效的语句,会生成编译时错误:

10 = 20;

小结:

数据类型的本质是一个模子

(2)数据类型代表需要占用的内存大小

(3)变量的本质是一段内存的别名

(4)变量隶属于某一种数据类型

(5)变量所在的内存大小取决其所属的数据类型

4. 有符号与无符号

计算机的符号位:数据类型的最高位用于标识数据的符号(最高位为1——负数,最高位为0——正数)

#include 

int main()
{
    char c = -5;
    short s = 6;
    int i = -7;

    //判断最高位是否是0,0表示正数,1表示负数
    printf("%d\n", ((c & 0x80) != 0 ));      //1,按位与,结果非0为负数
    printf("%d\n", ((s & 0x8000) != 0));     //0,为正数
    printf("%d\n", ((c & 0x80000000) != 0));  //1,为负数

    return 0;
}

 4.1 有符号数的表示法

(1)在计算机内部用补码表示有符号数

  ①正数的补码为正数本身

  ②负数的补码为负数的绝对值各位取反加1

    8位整数 5的补码为:   0000 0101

    8位整数-7的补码为:   1111 1001

    16位整数20的补码为:  0000 0000 0001 0100

    16位整数-13的补码为: 1111 1111 1111 0011

4.2 无符号数的表示法

(1)在计算机内部用原码表示无符号数

  ①无符号数默认为正数

  ②无符号数没有符号位

(2)对于固定长度的无符号数

  ①MAX_VALUE+1  → MIN_VALUE

  ②MIN_VALUE-1  → MAX_VALUE

(3)signed和unsigned

  ①C语言中变量默认为有符号的类型

  ②用unsigned关键字声明变量为无符号类型——只有整数类型能够声明为unsigned(浮点数不能声明为无符号数)

    int i;  //默认为有符号整数;

    signed int j; //显式声明变量为有符号整数;

    unsigned int k ; //声明为无符号整数

【编程实验1】当无符号数遇上有符号数时→有符号会被看作无符号数!

#include

int main()
{
    unsigned int i = 5;
    int j = -10;

    if ((i + j) > 0)   //j转换为无符号数,变成为大的正数
    {
        printf("i+j>=0\n"); //该结果为运行后最终的输出结果!
    }
    else
    {
        printf("i+j<=0\n");
    }

    return 0;
}

【编程实验2】错误地使用unsigned

int main()
{
    unsigned int i = 0;     //无符号数永远为正数,即最小值为0

    //当i减小到为0时,然后再减1就变成无符号int型数的最大值,
    //以下代码会进入死循环,所以不应用无符号数来作为循环变量!
    for (i = 9; i >= 0; i--) //i为负数即退出循环,但i为无符号数,不可能为负数
    {
        printf("i=%u\n", i);
    }

    return 0;
}

4. 小结

(1)有符号数用补码表示

(2)无符号数用原码表示

(3)unsigned只能修饰整数类型的变量

   当无符号数与有符号数混合计算时,会将有符号数转换为无符号数后再进行计算,结果为无符号数。

    1. 浮点数的秘密
      1. 1. 内存中的浮点数——存储方式:符号位、指数、尾数的符号

C/C++ 变量、结构体、数组存储本质_第2张图片

float与double类型的数据在计算机内部的表示法是相同的,但由于所占存储空间不同,分别能够表示的数据值范围和精度不同。

4.3 浮点数的存储示例

2.1 浮点数的转换步骤

(1)将浮点数转换成二进制

(2)用科学计数法表示二进制浮点数

(3)计算指数偏移后的值:(需加上偏移量,float型:+127,double型:+1023)

   示例:对于指数6,偏移后的值如下:

  float: 127 + 6→ 133

  double: 1023 +6 → 1029

2.2 以float型的实数8.25在内存表示法为例,演示转换过程

(1)8.25的二进制表示法:1000.01,再转为指数形式:1.00001*(2^3)

  ①符号位:0

  ②指数为3:  127+3 =130 → 10000010

  ③小数:00001。   //要转为尾数,须在后面补0

(2)内存中8.25的float表示:0 1000 0010 000 01000000 0000 0000 0000 =0x41040000

      (红色部分为符号位,指数为绿色,尾数为紫色部分加补零后的23位)

【编程实现1】十进制浮点数的内存表示

#include

int main()

{
    float f = 8.25;

    //为了用16进制显示出浮点数的内存表示的二进制数值,可将那
    //段内存以整数形式去显示那串二进制数,以便人类阅读
    unsigned int* p = (unsigned int*)&f; //整型指针指向f的内存
    printf("0x%08X\n", *p);  //以整型输出0x41040000
    return 0;
}

4.4  浮点类型的秘密

(1)思考:int和float都占4个字节的内存,而int类型的范围:[-231,231-1],float类型的范围:[-3.4*1038,3.4*1038],为float却比int的范围大得多呢?

(2)秘密

  ①float能表示的具体数字个数与int相同

  ②float可表示的数字之间是不连续的,存在跳跃

  ③float只是一种近似的表示法,不能作为精确数使用

  ④由于内存表示法相对复杂,float的运算速度比int慢很多

(3)注意:double与float具有相同的内存表示法,因为double也不是精确的。由于double占用的内存较多,所能表示的精度比float高。

【编程实现】float类型的不精确示例

#include

int main()

{
    float f1 = 3.1415f;
    float f2 = 123456789;

    //精确到小数点后面的10位
    printf("%0.10f\n", f1);//3.1414999962  -->不精确
    printf("%0.10f\n", f2);//123456792.0000000000 -->不连续
    //同理可实验得,float型的123456780-123456788之间的在内存中都
    //是用123456784.0000000000这个数表示的。

    return 0;
}

小结

(1)浮点类型与整数类型的内存表示法不同

(2)浮点类型的内存表示更复杂

(3)浮点类型可表示的范围更大

(4)浮点类型是一种不精确的类型

(5)浮点类型的运算速度较慢

C 数组允许定义可存储相同类型数据项的变量,结构是 C 编程中另一种用户自定义的可用的数据类型,它允许您存储不同类型的数据项。

结构用于表示一条记录,假设您想要跟踪图书馆中书本的动态,您可能需要跟踪每本书的下列属性:

(1)C语言中的struct可以看作变量的集合

(2)struct的问题——空结构体占用多的内存?

柔性数组即数组大小待定的数组

(2)C语言中可以由结构体产生柔性数组

(3)C语言中结构体的最后一个元素可以是大小未知的数组

struct SoftArray
{
  int len;
  int array[]; //array仅是一个待使用的标识符。与指针不同,编译器
               //并不为array变量分配空间,因为也不知道array究竟
               //多大。只是用来作为一个标识符,以便以后可以通过这
               //个标识符来访问其中的内容。所以sizeof(SoftArray)=4
}

2. C语言中的union

共用体是一种特殊的数据类型,允许您在相同的内存位置存储不同的数据类型。您可以定义一个带有多成员的共用体,但是任何时候只能有一个成员带有值。共用体提供了一种使用相同的内存位置的有效方式。

定义共用体

为了定义共用体,您必须使用 union 语句,方式与定义结构类似。union 语句定义了一个新的数据类型,带有多个成员。union 语句的格式如下:

(1)C语言中的union在语法上与struct相似

(2)union只分配最大成员的空间,所有成员共享这个空间

union [union tag]
{
   member definition;
   member definition;
   ...
   member definition;
} [one or more union variables];

union tag 是可选的,每个 member definition 是标准的变量定义,比如 int i; 或者 float f; 或者其他有效的变量定义。在共用体定义的末尾,最后一个分号之前,您可以指定一个或多个共用体变量,这是可选的。下面定义一个名为 Data 的共用体类型,有三个成员 i、f 和 str:

union Data { int i; float f; char str[20]; } data;

现在,Data 类型的变量可以存储一个整数、一个浮点数,或者一个字符串。这意味着一个变量(相同的内存位置)可以存储多个多种类型的数据。您可以根据需要在一个共用体内使用任何内置的或者用户自定义的数据类型。

共用体占用的内存应足够存储共用体中最大的成员。例如,在上面的实例中,Data 将占用 20 个字节的内存空间,因为在各个成员中,字符串所占用的空间是最大的。下面的实例将显示上面的共用体占用的总内存大小:

访问共用体成员

为了访问共用体的成员,我们使用成员访问运算符(.)。成员访问运算符是共用体变量名称和我们要访问的共用体成员之间的一个句号。您可以使用 union 关键字来定义共用体类型的变量。下面的实例演示了共用体的用法:

在这里,我们可以看到共用体的 i 和 f 成员的值有损坏,因为最后赋给变量的值占用了内存位置,这也是 str 成员能够完好输出的原因。现在让我们再来看一个相同的实例,这次我们在同一时间只使用一个变量,这也演示了使用共用体的主要目的:

3. 

enum(枚举)

枚举是 C 语言中的一种基本数据类型,它可以让数据更简洁,更易读。

枚举语法定义格式为:

enum 枚举名 {枚举元素1,枚举元素2,……};

接下来我们举个例子,比如:一星期有 7 天,如果不用枚举,我们需要使用 #define 来为每个整数定义一个别名:

#define MON 1 #define TUE 2 #define WED 3 #define THU 4 #define FRI 5 #define SAT 6 #define SUN 7

这个看起来代码量就比较多,接下来我们看看使用枚举的方式:

enum DAY
{
      MON=1, TUE, WED, THU, FRI, SAT, SUN
};

这样看起来是不是更简洁了。

注意:第一个枚举成员的默认值为整型的 0,后续枚举成员的值在前一个成员上加 1。我们在这个实例中把第一个枚举成员的值定义为 1,第二个就为 2,以此类推。

可以在定义枚举类型时改变枚举元素的值:

enum season {spring, summer=3, autumn, winter};

没有指定值的枚举元素,其值为前一元素加 1。也就说 spring 的值为 0,summer 的值为 3,autumn 的值为 4,winter 的值为 5

枚举变量的定义

前面我们只是声明了枚举类型,接下来我们看看如何定义枚举变量。

我们可以通过以下三种方式来定义枚举变量

1、先定义枚举类型,再定义枚举变量

enum DAY
{
      MON=1, TUE, WED, THU, FRI, SAT, SUN
};
enum DAY day;

2、定义枚举类型的同时定义枚举变量

enum DAY
{
      MON=1, TUE, WED, THU, FRI, SAT, SUN
} day;

3、省略枚举名称,直接定义枚举变量

enum
{
      MON=1, TUE, WED, THU, FRI, SAT, SUN
} day;

实例

#include enum DAY { MON=1, TUE, WED, THU, FRI, SAT, SUN }; int main() { enum DAY day; day = WED; printf("%d",day); return 0; }

以上实例输出结果为:

3

在C 语言中,枚举类型是被当做 int 或者 unsigned int 类型来处理的,所以按照 C 语言规范是没有办法遍历枚举类型的。

不过在一些特殊的情况下,枚举类型必须连续是可以实现有条件的遍历。

以下实例使用 for 来遍历枚举的元素:

实例

#include enum DAY { MON=1, TUE, WED, THU, FRI, SAT, SUN } day; int main() { // 遍历枚举元素 for (day = MON; day <= SUN; day++) { printf("枚举元素:%d \n", day); } }

以上实例输出结果为:

枚举元素:1 
枚举元素:2 
枚举元素:3 
枚举元素:4 
枚举元素:5 
枚举元素:6 
枚举元素:7

以下枚举类型不连续,这种枚举无法遍历。

enum
{
    ENUM_0,
    ENUM_10 = 10,
    ENUM_11
};

枚举在 switch 中的使用:

实例

#include #include int main() { enum color { red=1, green, blue }; enum color favorite_color; /* 用户输入数字来选择颜色 */ printf("请输入你喜欢的颜色: (1. red, 2. green, 3. blue): "); scanf("%u", &favorite_color); /* 输出结果 */ switch (favorite_color) { case red: printf("你喜欢的颜色是红色"); break; case green: printf("你喜欢的颜色是绿色"); break; case blue: printf("你喜欢的颜色是蓝色"); break; default: printf("你没有选择你喜欢的颜色"); } return 0; }

以上实例输出结果为:

#include 

enum                   //无名枚举,用于定义常量
{
    ARRAY_SIZE = 10    //定义数组大小,ARRAY_SIZE是常量,运行时无法改变
};

enum Color
{
    RED    = 0x00FF0000,
    GREEN  = 0x0000FF00,
    BLUE   = 0x000000FF  //注意,后面没分号
};

//打印,参数为枚举类型
void PrintColor(enum Color c)
{
    switch( c )
    {
        case RED:
            printf("Color: RED (0x%08X)\n", c);
            break;
        case GREEN:
            printf("Color: GREEN (0x%08X)\n", c);
            break;
        case BLUE:
            printf("Color: BLUE(0x%08X)\n", c);
            break;
    }
}

//初始化数据
void InitArray(int array[])
{
    int i = 0;
    
    for(i=0; i

(1)enum是C语言中的一种自定义类型

(2)enum值是可以根据需要自定义的的整型值

(3)第一个定义的enum值默认为0。

(4)默认情况下的enum值是在前一个定义值的基础上加1

(5)enum类型的变量只能取定义时的离散值

        1. 枚举类型的特殊意义

(1)enum中定义的值是C语言中真正意义上的常量

(2)在工程中enum多用于定义整型常量

小结:

(1)struct中的每人数据成员有独立的存储空间

(2)struct可以通过最后的数组标识符产生柔性数组

(3)union中的所有数据成员共享同一个存储空间

(4)union的使用会受到系统大小端的影响

变量实际上只不过是程序可以操作的存储区的名称,C++中每个变量或者对象都有指定的类型。类型决定了变量存储的大小和布局,该范围内的值都可以存储在内存中,运算符可以应用于变量或者对象上。C++变量基本上和C变量相同,如果认为对象也是一个变量的话,这个相比于C是多出来的一部分内容。

1. C++变量作用域

C语言的变量只有全局和局部的区分,局部变量可以在全局使用,局部变量只有在函数体内生效。变量作用域主要分为三种类型:

  • 在函数或一个代码块内部声明的变量,称为局部变量。

  • 在函数参数的定义中声明的变量,称为形式参数。

  • 在所有函数外部声明的变量,称为全局变量。

局部变量

在函数或一个代码块内部声明的变量,称为局部变量。它们只能被函数内部或者代码块内部的语句使用。下面的实例使用了局部变量:

#include 
using namespace std;
 
int main ()
{
  // 局部变量声明
  int a, b;
  int c;
 
  // 实际初始化
  a = 10;
  b = 20;
  c = a + b;
 
  cout << c;
 
  return 0;
}

全局变量

在所有函数外部定义的变量(通常是在程序的头部),称为全局变量。全局变量的值在程序的整个生命周期内都是有效的。

全局变量可以被任何函数访问。也就是说,全局变量一旦声明,在整个程序中都是可用的。下面的实例使用了全局变量和局部变量:

#include 
using namespace std;
 
// 全局变量声明
int g;
 
int main ()
{
  // 局部变量声明
  int a, b;
 
  // 实际初始化
  a = 10;
  b = 20;
  g = a + b;
 
  cout << g;
 
  return 0;
}

 在程序中,局部变量和全局变量的名称可以相同,但是在函数内,局部变量的值会覆盖全局变量的值。下面是一个实例:

#include 
using namespace std;
 
// 全局变量声明
int g = 20;
 
int main ()
{
  // 局部变量声明
  int g = 10;
 
  cout << g;
 
  return 0;
}

初始化局部变量和全局变量

当局部变量被定义时,系统不会对其初始化,您必须自行对其初始化。定义全局变量时,系统会自动初始化为下列值:

数据类型 初始化默认值
int 0
char '\0'
float 0
double 0
pointer NULL

正确地初始化变量是一个良好的编程习惯,否则有时候程序可能会产生意想不到的结果。

另外C++中通过命名空间将变量圈在一个固定的范围内,这一点是和之前C与相比较全局概念不一致的全局。

你可能感兴趣的:(C,C++,c语言,c++,开发语言)