#C语言基础 笔记四

 结构体

1. 定义:

用户自定义的数据类型,在结构体中可以包含若干个不同数据类型的成员变量(也可以相同),使这些数据项组合起来反映某一个信息

2. 格式:

struct 结构体名 (用户自定义的数据类型)

{

数据类型 成员变量1;

数据类型 成员变量2;

数据类型 成员变量2;

};

3. 结构体变量:

3.1. 概念:

通过结构体类型定义的变量

3.2. 格式:

struct 结构体名 变量名;

3.2.1. 先定义结构体,在定义结构体变量

struct 结构体名

{

        成员变量;

};

struct 结构体名 变量名;

#include <stdio.h>

struct student
{
    char name[32];
    int id;
    int age;
};

int main(int argc, char const *argv[])
{
    struct student stu;
    
    return 0;
}
3.2.2. 定义结构体的同时,定义结构体变量

struct 结构体名

{

        成员变量;

} 变量名;

struct student
{
    char name[32];
    int id;
    int age;
} stu;
3.2.3. 缺省结构体名定义结构体变量

struct

{

        成员变量;

} 变量名;

4. 赋值:

4.1. 定义变量的同时直接用大括号赋值
#include <stdio.h>
struct student
{
    char name[32];
    int id;
    int age;
};
int main(int argc, char const *argv[])
{
    struct student stu = {"zhangsan", 153461, 18};
    
    return 0;
}
4.2. 定义变量时未初始化,然后对变量单独赋值
#include <stdio.h>
#include <string.h>

struct student
{
    char name[32];
    int id;
    int age;
};

int main(int argc, char const *argv[])
{
    struct student stu;
    stu.age = 18;
    stu.id = 3517635;
    strcpy(stu.name, "zhangsan");
    
    return 0;
}
4.3. 点等法赋值
#include <stdio.h>
#include <string.h>

struct student
{
    char name[32];
    int id;
    int age;
};

int main(int argc, char const *argv[])
{
    struct student stu = {
        .id = 361867,
        .age = 18,
        .name = "zhangsan",
    };
    
    return 0;
}

5. 访问

通过 . 访问:结构体变量名.成员变量名

scanf("%s %d %d", stu.name, &stu.age, &stu.id);

printf("%s %d %d\n", stu.name, stu.age, stu.id);

6. 重定义 typedef

typedef int int_num;

int a; == int_num a;

typedef int * int_p;

int *p = &a; == int_p p = &a;

6.1. 定义结构体的同时重定义

typedef struct student

{

int id;

int age;

} STU; // STU 是结构体类型重定义的名字

struct student stu; == STU stu;

6.2. 先定义结构体,然后重定义

struct student

{

int id;

int age;

};

typedef struct student STU;

STU stu;

例子:
typedef struct student
{
	int id;
	int age;
} STU;
STU stu = {1, 18};
STU *p = &stu;  // struct student * p = &stu

int a;
typedef int STU
STU *p = &a;


typedef struct student
{
    int id;
    int age;
} STU, *STUP;
typedef struct student STU
typedef struct student * STUP
STU stu = {1, 21};
STUP p = &stu;
STU *q = &stu;  //  STUP p = &stu;

练习:创建一个名为student的结构体,包含姓名,学号,班级,分数,(数据类型自己定义),从终端输入学生的信息并打印。

练习:用指针实现strcpy、strcat

strcpy:

#include <stdio.h>

char *mystrcpy(char *s1, char *s2)
{
    char *p = s1;
    while (*s2 != '\0')
    {
        *s1 = *s2;
        s1++;
        s2++;
    }
    *s1 = '\0';

    return p;
}

int main(int argc, char const *argv[])
{
    char str[32] = {};
    char arr[32] = "hello";
    mystrcpy(str, arr);
    printf("%s\n", str); // hello
    return 0;
}

结构体数组

1. 概念

结构体类型相同的变量组成的数组

2. 格式:

2.1. 定义结构体的同时定义结构体数组
struct student
{
    int id;
    int age;
} stu[3];
2.2. 先定义结构体,然后定义结构体数组
struct student
{
    int id;
    int age;
};

struct student stu[3];

3. 初始化

3.1 定义结构体数组同时赋值
struct student
{
    int id;
    int age;
} stu[3] = {
    {3415341, 21},  // 这是第一个人的信息
    {35134, 18},    // 这是第二个人的信息
    {763163, 23}    // 这是第三个人的信息
};
3.2. 先定义结构体数组,在对结构体数组的每一个元素分别赋值
struct student
{
    int id;
    int age;
} stu[3];

stu[0].id = 1;
stu[0].age = 18;
stu[1].id = 2;

4. 结构体数组的大小

sizeof(结构体数组名);

元素个数*结构体类型大小;

5. 结构体数组输入输出(for循环)

#include <stdio.h>

struct student
{
    int id;
    int age;
    float score;
    char *name;
} stu[3];

int main(int argc, char const *argv[])
{
    for (int i = 0; i < 3; i++)
    {
        scanf("%d %d %f", &stu[i].id, &stu[i].age, &stu[i].score);
    }
    for (int i = 0; i < 3; i++)
    {
        printf("%d %d %.2f\n", stu[i].id, stu[i].age, stu[i].score);
    }
    
    return 0;
}

练习: 创建一个名为student的结构体数组,包含学号,姓名,分数,(数据类型自己定义),从终端输入学生的信息并打印分数及格的学生信息(输入3人即可)。

结构体指针

1. 概念:

指向结构体的指针

2. 格式:

struct 结构体名 *结构体指针名

#include <stdio.h>

struct student
{
    int id;
    int age;
} stu1, stu2;

struct work
{
    int id;
    int age;
} w1, w2;

int main(int argc, char const *argv[])
{
    struct student *p1 = &stu1;
    struct student *p2 = &w1;  // 错误,结构体类型不匹配
    return 0;
}

3. 赋值

格式:结构体指针变量名 -> 成员变量名

-> 指向的

#include <stdio.h>

struct student
{
    int id;
    int age;
} stu1, stu2;

int main(int argc, char const *argv[])
{
    struct student *p1 = &stu1;
    p1 -> id = 1;
    p1 -> age = 18;
    printf("%d %d\n", p1 -> id, p1 -> age);
    return 0;
}

(*p1).成员变量

练习:创建一个结构体数组,数组名为student,成员包含学号,姓名,成绩(数据类型自己设定)从终端输入学生信息,封装函数实现按成绩从低到高打印学生信息。

4. 结构体指针大小

本质是指针,4字节

总结

1. 不能把结构体类型变量作为整体引用,只能对结构体类型变量中的成员变量分别引用

2. 如果成员变量本身属于另一种结构体类型,用若干个成员运算符一级级找到你想要的成员变量

3. 可以把成员变量当成普通变量运算

4. 在数组中,数组之间不能彼此赋值,结构体变量可以相互赋值

inta[3]={};

intb[3]={1, 2, 3};

a=b;

结构体大小

sizeof(struct 结构体名);//结构体类型大小

struct stu
{
    char a;
    short w;
    char y;
    int b;
    char c;
};
sizeof(struct stu); // 12

结构体大小遵循字节对齐原则

1. 字节对齐

在实际使用中,访问特定数据类型变量时需要在特定的内存起始地址进行访问,这就需要各种数据类型按照一定的规则在空间上进行排列,而不是顺序地一个接一个地存放,这就是字节对齐

2. 字节对齐原则

a. 在32位系统下默认的value值为4字节,判断结构体类型中最大成员变量的字节大小,和默认的value值进行比较,按小的数进行对齐

b. 结构体成员进行对齐时遵循地址偏移量是成员类型的整数倍,double 类型数据存放在4字节的整数倍

c. 结构体成员按顺序进行存储,如果不满足以上条件时,需要填充空字节

3. 为什么要进行字节对齐?

a. 平台原因:不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。(提高程序的移植性)

b. 性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。

c. 内存原因:在设计结构体时,通过对齐规则尽可能优化结构体的空间大小至最小空间

例子:

struct S
{
    double a;
    char b;
    int c;
}

第一步:找出成员变量的大小,让其与编译器的默认的对齐数进行比较,取较小的值作为该成员变量的对齐数

注:我们现在使用的编译器默认对齐数为4

VS编译器默认对齐数为8

第二步:根据每个成员对应的对齐数画出他们在内存中的相对位置

第三步:通过最大对齐数决定最终该结构体的大小

通过图片我们可以知道。绿色部分+红色部分紫色部分+红色与紫色之间的白色部分(浪费掉的)总共占用了16字节的内存空间

补充:

指针数组

命令行参数
int main(int argc, char const *argv[])
{
    return 0;
}

argv:就是一个指针数组,里面存方法的是命令行传递的字符串

argc:表示argv指针数组里面存放数据的个数,即命令行传递字符串的个数

共用体

不同类型的成员变量共用一块地址空间

1. 格式

union共用体名

{

成员列表;

};

2. 定义共用体变量

union共用体名变量名;

union val

{

int a;

char ch;

}

union valv;

v.a=10;

//他俩不能同时出现,是以最后一次赋值为准

v.ch='a';

3. 特性

1) 共用体成员共用同一块地址空间

2) 赋值顺序以最后一次为准

3) 共用体大小为成员中类型最大的数据的大小

4. 使用共用体测试大小端

#include <stdio.h>

union val
{
    int num;
    char ch;
};

int main(int argc, char const *argv[])
{
    union val v;
    v.num = 0x12345678;
    if(v.ch == 0x78)
    {
        printf("小端\n");
    }
    else
    {
        printf("大断\n");
    }
    return 0;
}

枚举

维基百科的理解:枚举类型用于声明一组命名的常数,当一个变量有几种可能的取值时,可以将它定义为枚举类型。 定义:是指将变量的值一一列出来,变量的值只限于列举出来的值的范围内。

我的理解:枚举类型就是将一些比较固定的值一一列举出来,比如一年有十二个月,一个礼拜有七天,这是毫无疑问的,就可以将这些月份天数用常量来代替。枚举类型和宏定义是差不多的,只有细微区别,宏运行是在预处理阶段完成的,枚举类型是在与编译阶段完成的。

1. 定义:

用户自定义数据类型,用于声明一组常数

2. 格式

enum枚举名

{

value1,

value2,

value3,

.....

};

#include <stdio.h>

enum week
{
    MON,
    TUE,
    WED
};

int main(int argc, char const *argv[])
{
    printf("%d %d %d\n", MON, TUE, WED); // 0 1 2
    int n;
    scanf("%d", &n);
    switch (n)
    {
    case MON:
        printf("周一\n");
        break;
    case TUE:
        printf("周二\n");
        break;
    case WED:
        printf("周三\n");
        break;
    }
    return 0;
}

未赋初值,第一个常数会默认为0,依次加一(如果第一个值被设为1,则默认从1开始递增)。

存储类型

auto static extern register

1. auto

修饰变量,一般省略时会认为是auto类型

2. static修饰变量函数

修饰变量:

1. 变量存在全局区(静态区)

如果静态局部变量有初值,存放在.data区,没有初值存放在.bss区域

2. 生命周期为整个程序

3. 限制作用域:

a. 修饰局部变量,和普通的局部变量的作用域没有区别,但是生命周期被延长为整个程序

b. 修饰全局变量,限制在本文件中使用

4. 只初始化一次,初值赋值0;

tatic 关键字在 C++ 中用于修饰局部变量、全局变量以及函数时,具有不同的特点和作用。

修饰局部变量

当 static 修饰局部变量时,其主要特点如下:

  1. 延长生命周期:静态局部变量在程序执行期间一直存在,其生命周期从首次初始化开始,一直持续到程序结束。这意味着,即使在函数返回后,静态局部变量的值也会被保留下来。
  2. 初始化:静态局部变量在首次进入其声明所在的作用域时被初始化,且只被初始化一次。
  3. 作用域限制:静态局部变量只在其声明所在的作用域内可见,其他函数无法直接访问它,但可以通过返回值或引用间接访问。
  4. 内存存储:静态局部变量在内存中存储于静态数据区,而不是在栈上分配空间。

修饰全局变量

当 static 修饰全局变量时,其主要特点在于限制变量的可见性:

  1. 文件内可见:被 static 修饰的全局变量只对定义在同一文件中的函数可见。其他文件即使包含了该变量的声明,也无法直接访问它。
  2. 内存存储:未初始化的静态全局变量存放在 BSS 段,已经初始化的则放在 DATA 段。

修饰函数

当 static 修饰函数时,其主要特点如下:

  1. 文件内调用:被 static 修饰的函数只能在同一文件中被调用。这有助于隐藏函数的实现细节,防止其他文件误用或修改。
  2. 访问方式:由于静态函数具有文件作用域,因此无法通过其他文件的函数指针来调用它。但在同一文件内,可以通过函数名直接调用。

总结

static 关键字通过控制变量的生命周期、可见性以及函数的可见性,帮助程序员更好地组织和管理代码。它有助于隐藏实现细节,提高代码的安全性和可维护性。同时,通过限制变量的作用域和函数的调用范围,可以减少潜在的错误和冲突。

#include <stdio.h>

void fun()
{
    static int a = 0;
    a++;
    printf("%d\n", a);
}

int main(int argc, char const *argv[])
{
    
    fun();  // 1
    fun();  // 2
    
    return 0;
}

3. extern外部引用

通过extern可以引用其他文件的全局变量或函数

4. register寄存器类型

由于寄存器数量比较少,申请不到空间时和auto一样

练习:

创建一个结构体数组,数组名为book,结构体成员包含编号,书名,售价(数据类型自己设定)。写一个函数,包含两个形参,分别接收结构体数组的首地址和一个指定的售价,函数的功能为打印结构体数组中售价大于指定售价的书的信息。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值