【C语言初阶笔记】初识结构体

目录

 

结构体

结构体的声明

结构的基础知识

结构的声明

声明的重点

结构成员的类型

结构体变量的定义和初始化

结构体成员的访问

结构体传参


结构体

结构体很重要,初学者一定要掌握。本章只是结构体的初识,让大家先做个了解,并不深入介绍,在进阶结构体部分博主会深入介绍。大家敬请期待吧!!


结构体的声明

结构的基础知识

结构体是由一系列具有相同类型或不同类型的数据构成的数据集合,叫做结构。
在C语言中,结构体指的是一种数据结构,是C语言中聚合数据类型的一类。结构体可以被声明为变量、指针或数组等,用以实现较复杂的数据结构。结构体同时也是一些元素的集合,这些元素称为结构体的成员,且这些成员可以为不同的类型,成员一般用名字访问。

比如存储一个班级学生的信息,肯定包括姓名、学号、性别、年龄、成绩、家庭地址等项。这些项都是具有内在联系的,它们是一个整体,都表示同一个学生的信息。但如果将它们定义成相互独立的变量的话,就无法反映它们的内在联系:

char name[20];   //姓名
int num;         //学号
char sex;        //性别
int age;         //年龄
float score;     //成绩
char addr[30];   //家庭住址

而且问题是这样写的话,只是定义了一个学生,如果要定义第二个学生就要再写一遍。这样不仅麻烦,而且很容易混淆。要是能定义一个变量,而且这个变量正好包含这六个项,即将它们合并成一个整体的话就好了。

结构体就是为了解决这个问题而产生的。结构体是将不同类型的数据按照一定的功能需求进行整体封装,封装的数据类型与大小均可以由用户指定。

之前讲的那些基本数据类型只能满足一些基本的要求,只能表示具有单一特性的简单事物。但是对于一些有很多特性的复杂事物,每一个特性就是一个基本类型。这个复杂的事物是由很多基本类型组合在一起而生成的一个比较复杂的类型。这时就需要运用结构体。

结构的声明

声明一个结构体类型的一般形式为:

struct结构体名
{
    成员列表
};

又或者是:

struct tag
{
 member-list;//成员列表
}variable-list;//全局结构体变量

例如描述一个学生:

struct STUDENT
{
char name[20];
int num;
char sex;
int age;
float score;
char addr[30];
};  //最后的分号千万不能省略

声明的重点

1.最后的分号千万不能省略。为了防止最后忘记分号,最好先将框架写出来,写的时候直接把分号加上:

struct STUDENT
{};

然后再将大括号打开:

struct STUDENT
{
};

再在里面书写内容。

2.结构体类型是由一些基本数据类型组合而成的新的数据类型。因为结构体类型中的成员是由程序员人为定义的,所以结构体类型是由我们人为定义的数据类型。

3.struct 是声明结构体类型时必须使用的关键字,不能省略。“结构体”这个词是根据英文单词 structure 译出的。

4. struct STUDENT 是定义的数据类型的名字,它向编译系统声明这是一个“结构体类型”,包括 name、num、sex、age、score、addr 等不同类型的项。

5. struct STUDENT 与系统提供的 int、char、float、double 等标准类型名一样,都是数据类型,具有同样的作用,都是用来定义变量的。

但结构体类型和系统提供的标准类型又有所不同:“结构体类型”不仅要求指定该类型为“结构体类型”,即 struct,而且要求指定该类型为某一“特定的”结构体类型,即“结构体名”。因为只有 struct 才是关键字,而“结构体名”是由编程人员自己命名的。所以说,“结构体类型”不是由系统提供的,而是由编程人员自己指定的。


这也就意味着,根据“结构体名”的不同,可以定义无数种“具体的”、“特定的”结构体类型。所以结构体类型并非是固定的一种类型。而 int 型、char 型、float 型、double 型都是固定的类型。

6. “结构体名”的命名规范是全部使用大写字母

7. “结构体名”是结构体类型的标志。花括号内是该结构体的各个成员,它们共同组成一个整体。对各个成员都要进行类型声明,如:

  1. char name[20];
  2. int num;
  3. char sex;
  4. int age;
  5. float score;
  6. char addr[30];

成员名的命名规则与变量名相同。

8. 声明结构体类型仅仅是声明了一个类型,系统并不为之分配内存,就如同系统不会为类型 int 分配内存一样。只有当使用这个类型定义了变量时,系统才会为变量分配内存。所以在声明结构体类型的时候,不可以对里面的变量进行初始化。


结构成员的类型

结构的成员可以是标量、数组、指针,甚至是其他结构体.

结构体变量的定义和初始化

以上只是声明了一个数据类型——“结构体类型”。它只是一个类型,与 int、char、float、double 一样,并没有具体的数据,系统也不会给它分配实际的内存单元。要想在程序中使用“结构体类型”数据,必须要定义“结构体类型变量”,并在其中存放具体的数据。就比如:

int a;

其中 int 是类型,而 a 是用这个类型定义的变量。结构体也是一样的,上面只是声明了一个类型,而这里要使用这个类型来定义变量,就这么简单。一个是类型,一个是用这个类型定义的变量。

那么如何定义结构体类型变量呢?有两种方法:

第一种方法是先声明“结构体类型”,再定义“结构体类型变量”。这种方式比较自由!


结构体类型的声明和函数声明一样,如果在所有函数,包括main函数的前面进行声明,那么就可以在所有函数中直接用它来定义变量;但如果是在某个函数中进行声明,那么只能在该函数中用它来定义变量。

一般我们都是在所有函数前面声明结构体类型,就同我们希望在所有函数中都可以使用int来定义变量一样。但是正如前面所讲,不建议使用全局变量,所以同样我们也不建议使用结构体类型定义的全局变量。我们都是在所有函数前对结构体类型进行声明,然后在某个函数中再定义局部的结构体类型变量。

比如在所有函数前定义了一个结构体类型 struct STUDENT,那么就可以在所有函数中使用它来定义局部的结构体类型变量。如:

struct STUDENT stud1, stud2;

stud1 和 stud2 就是我们定义的结构体变量名。定义了结构体变量之后,系统就会为之分配内存单元。与前面讲的局部变量一样,如果 stud1 和 stud2 是在某个函数中定义的局部变量,那么就只能在该函数中使用。在其他函数中可以定义重名的结构体变量而不会相互产生影响。

第二种方法是在声明结构体类型的同时定义结构体变量。这就意味着,如果你在所有函数前声明结构体类型,那么定义的变量就是全局变量;而如果要定义局部变量,那么就只能在某个函数中对结构体类型进行声明,从而导致只能在这个函数中使用这个类型。

那么声明的时候是如何定义变量的呢?我们知道,声明的时候最后有个一分号,就在那个分号前写上你想定义的变量名就行了,如:

struct STUDENT

{

char name[20];

int num;

char sex;

int age;

float score;

char addr[30];

}stud;

这样就声明了一个结构体类型,并用这个类型定义了一个结构体变量 stud。这个变量是一个全局变量

“结构体类型”的声明和使用与函数的定义和使用有所不同,函数的定义可以放在调用处的后面,只需在前面声明一下即可。但是“结构体类型”的声明必须放在“使用结构体类型定义结构体变量”的前面。

如果程序规模比较大,往往会将结构体类型的声明集中放到一个以 .h 为后缀的头文件中。哪个源文件需要用到此结构体类型,只要用 #include 命令将该头文件包含到该文件中即可,这样做便于修改和使用。

我们来看个例子:

struct Point
{
 int x;
 int y;
}p1; //声明类型的同时定义变量p1
struct Point p2; //定义结构体变量p2
//初始化:定义变量的同时赋初值。
struct Point p3 = {x, y};
struct Stu        //类型声明
{
 char name[15];//名字
 int age;      //年龄
};
struct Stu s = {"zhangsan", 20};//初始化
struct Node
{
 int data;
 struct Point p;
 struct Node* next; 
}n1 = {10, {4,5}, NULL}; //结构体嵌套初始化
struct Node n2 = {20, {5, 6}, NULL};//结构体嵌套初始化

结构体成员的访问

结构体变量访问成员 结构变量的成员是通过点操作符(.)访问的。点操作符接受两个操作数。 例如:

struct Stu
{
	char name[20];
	int age;

};
struct Stu S;

我们可以看到 s 有成员 name 和 age ; 那我们如何访问s的成员?

struct S s;
strcpy(s.name, "zhangsan");//使用.访问name成员
s.age = 20;//使用.访问age成员

结构体指针访问指向变量的成员 有时候我们得到的不是一个结构体变量,而是指向一个结构体的 指针。 那该如何访问成员。 如下:

struct Stu
{
 char name[20];
 int age;
};
void print(struct Stu* ps)
{
 printf("name = %s   age = %d\n", (*ps).name, (*ps).age);
    //使用结构体指针访问指向对象的成员
 printf("name = %s   age = %d\n", ps->name, ps->age);
}
int main()
{
    struct Stu s = {"zhangsan", 20};
    print(&s);//结构体地址传参
    return 0;
}

结构体传参

结构变量是一个标量,它可以用于其他标量可以使用的任何场合,但把结构体作为参数传递给一个函数要注重效率:

struct S
{
 int data[1000];
 int num;
};
struct S s = {{1,2,3,4}, 1000};
//结构体传参
void print1(struct S s)
{
 printf("%d\n", s.num);
}
//结构体地址传参
void print2(struct S* ps)
{
 printf("%d\n", ps->num);
}
int main()
{
 print1(s);  //传结构体
 print2(&s); //传地址
 return 0;
}

那么上面的 print1 和 print2 函数那个效率更高呢?

肯定是print2函数。

因为函数传参的时候,参数是需要压栈的。 如果传递一个结构体对象的时候,结构体过大,参数压栈 的的系统开销比较大,所以会导致性能的下降。

但是传递的参数是一个指向结构体的指针的话,指针比结构体小得多,所以把它压到堆栈上效率要提高很多,传递指针的代价是我们必须在函数中使用间接访问来访问结构的成员,结构越大,把它指向它的指针传递给函数的效率越高。

向函数传递指针的缺陷在于函数现在可以调用程序的结构变量进行修改,如果我们不希望如此,可以在函数调用中使用关键字const来防止这类修改。

结论

结构体传参的时候,要传结构体的地址。

 

你可能感兴趣的