《C语言程序设计》笔记p8

发布于:2025-08-07 ⋅ 阅读:(28) ⋅ 点赞:(0)

结构体字节对齐

尽可能让结构体的每一个成员都占据一个内存单元,提高程序运行的速度。

struct student {
    unsigned char age;
    double x;
    unsigned short score;
};
sizeof (struct student) // 24(8字节对齐)

_Alignof 运算符(C11)

获取对齐的字节数

_Alignof (类型名)
_Alignof (表达式)

示例

struct student {
    unsigned char age;
    double x;
    unsigned short score;
};

_Alignof (struct student) // 8

示例2

struct student {
    unsigned char age;
    unsigned short score;
};

_Alignof (struct student) // 2

联合体(Union)

联合体也叫共用体,他的定义和 结构体一样,只是他的每个成员变量都使用共同的内存(起点地址相同)。

union 联合体类型名 {
    成员类型1 成员变量1;
    成员类型2 成员变量2;
    ...
}

示例

#include <stdio.h>

union u_t{
 unsigned char buf[2];
 unsigned short s;
};

int main(int argc, char * argv[]) {
 union u_t u;

 printf("sizeof(u):%ld\n", sizeof(u));
 u.s = 258; // 00000001 000000002
 printf("u.s:%#x, u.buf[0]:%#x, u.buf[1]:%#x\n",
   u.s, u.buf[0], u.buf[1]);
     // 0x0102 0x02       0x01

 return 0;
}

大小端

大小端(Endianness)是指计算存储多字节数据(如:short int、int、float、double等)时,各个字节在内存中的排列顺序。

小端模式

低位字节存储在低地址,高位字节存储在高地址。

大端模式

低位字节存储在高地址,高位字节存储在低地址

如:

unsigned int x = 0x12345678;

小端表示

+------+------+------+------+
| 0x78 | 0x65 | 0x43 | 0x21 | 
+------+------+------+------+
低地址                    高地址

大端表示

+------+------+------+------+
| 0x21 | 0x43 | 0x65 | 0x21 | 
+------+------+------+------+
低地址                    高地址

检测方法

使用联合体进行检测,如上图所示,定义一个联合体

union u_t {
    unsigned char buf[4];
    unsigned int x;
}

将 整数 0x87654321 赋值给 x, 检查 buf[0] 是 0x21 就是小端,如果是 0x87 就是大端。

枚举类型

枚举类型(Enumeration) 是用于将一组整数常量用自定义类型来表达,使代码更加易读和维护。

语法

enum 枚举类型名 {
    标识符1 [= 整数值1], // 第一个默认值是0
    标识符2 [= 整数值2], // 后续默认值是前一个加1
    ...
};

示例

#include <stdio.h>

enum week {
 Mon = 1,
 Tue,
 Wed,
 Tru,
 Fri,
 Sat,
 Sun
};
int main(int argc, char * argv[]) {
 // 定义枚举类型的变量
 enum week w = Mon;
 printf("Sun:%d\n", Sun);
 printf("w:%d\n", w);

 return 0;
}


作用:

  • 提到可读性
  • 自动分配整数值
  • 限定取值范围

注意:

给枚举类型的变量赋值整数,有些编译器可能报错或警告。

计算机中常用的段

代码段

数据段

动态内存分配

动态内存分配是指从堆中动态的获取和释放内存。

常用函数

// 申请内存,给定字节数,返回内存的首地址,失败返回NULL。
void *malloc(size_t size);
// 释放内存,还回给系统。
void free(void *ptr);
// 申请内存,给定每个成员字节数和成员数量,返回内存的首地址。内存内容置零。
void *calloc(size_t nmemb, size_t size);
// 释放旧内存,返回新内存地址。
void *realloc(void *ptr, size_t size);

头文件: #include <stdlib.h>

示例:

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char * argv[]) {
 int * pint = NULL; // 定义一个指针,用来指向新分配的内存。
 // 申请 4 字节的内存用来存储 int 类型数据
 pint = (int*)malloc(4);
 if (pint != NULL) { 
  printf("申请内存成功\n");
  // 将 数值 100 放入这个内存
  *pint = 100;
  printf("内存存储的是:%d\n", *pint);
  if (pint) {
   free(pint);
   pint = NULL;
  }
 } else {
  printf("申请内存失败\n");
 }

 return 0;
}

在动态申请内存时,通常用指针记录内存的位置。通常指针为空则说明没有指向可使用的内存,不为空是则说明指向了已经分配的内存。

如果内存已经释放,则应及时将指针置空,否则将会成为悬空指针。

悬空指针:

悬空指针是指向不可用地址的指针。

内存碎片问题:

多次分配和释放内存,可能回导致没有大的连续的内存可用,导致内存分配失败。

注意事项:

  • 使用 malloc分配的内存未进行初始化,其内部可能是任意值,需要手动初始化。
  • malloc、calloc、realloc分配的内存必须使用 free 显式的释放。
  • 不要重复释放内存,对已经释放内存的指针再次调用free释放回导致致命结果。
  • malloc、calloc、realloc内存分配函数可能失败,失败返回NULL

示例

写一个程序,输入学生的数量n,使用动态内存分配的方法申请 n 个学生的需要的内存,存储 n 个学生的姓名、年龄和成绩。

struct student {
    char name[50];
    int age;
    int score;
};
  • 输入n 个学生的信息,
  • 打印 n 个学生的信息。

参考答案:

#include <stdlib.h>

struct student {
    char name[50];
    int age;
    int score;
};

int main(int argc, char * argv[]) {
 int n;
 struct student *pstu = NULL;
 // struct student pstu[20];

 printf("请输入人数:");
 scanf("%d", &n);
 // 申请内存
 pstu = (struct student*)malloc(sizeof(struct student) * n);
 if (pstu == NULL)
  return -1;
 // 输入
 for (int i=0; i < n; i++) {
  printf("请输入姓名:");
  scanf("%s", pstu[i].name);
  printf("请输入年龄:");
  scanf("%d", &pstu[i].age);
  printf("请输入成绩:");
  scanf("%d", &pstu[i].score);
 }
 // 打印
 for (int i=0; i < n; i++) {
  printf("姓名:%s ", pstu[i].name);
  printf("年龄:%d ", pstu[i].age);
  printf("成绩:%d\n", pstu[i].score);
 }
 free(pstu);
 pstu = NULL;

 return 0;
}

函数指针

函数是代码段的一段CPU指令,函数名是一个常量地址,是该函数的起始地址。使用指针可以指向这段地址,并可以调用该函数。

作用:

使用指针调用函数。

指向函数的指针称为函数指针:

定义语法

返回类型 (*指针变量名)(形参类型1, 形参类型2, 形参类型3);

示例

#include <stdio.h>

int myadd(int x, int y) {
 printf("myadd(%d, %d) is called\n", x, y);
 return x + y;
}

int mymul(int x, int y) {
 printf("mymul(%d, %d) is called\n", x, y);
 return x * y;
}

int main(int argc, char * argv[]) {
 // 定义函数指针
 int (*p_fun)(int,int) = mymul;

 // 使用函数指针来调用指向的函数
 p_fun(100, 200);
 p_fun = myadd;
 p_fun(300, 400);

 return 0;
}

复杂指针的定义方法

使用 (*) 替换变量名的办法:

// 指向一维数组的指针
int arr[10];
int (*arr)[10];
// 指向二维数组的指针
int * arr2d[3][4];
int * (*parr2d)[3][4];

// 指向函数的指针
int myadd(int x, int y);
int (*pmyadd)(int, int);
int x;
int *px;

int a[3][4];
a返回的类型为 int (*p) [4];

复杂指针的类型别名 typedef

// 指向函数的指针的类型别名
#include <stdio.h>

int myadd(int x, int y) {
 printf("myadd(%d, %d) is called\n", x, y);
 return x + y;
}

int mymul(int x, int y) {
 printf("mymul(%d, %d) is called\n", x, y);
 return x * y;
}
// 定义int(int,int) 类型函数的别名 PFUN;
typedef int(*PFUN)(int,int); 
int main(int argc, char * argv[]) {
 // 定义函数指针
 // int (*p_fun)(int,int) = mymul;
 PFUN p_fun = mymul;  // 等同于上述函数指针变量的定义

 // 使用函数指针来调用指向的函数
 p_fun(100, 200);
 p_fun = myadd;
 p_fun(300, 400);

 return 0;
}

网站公告

今日签到

点亮在社区的每一天
去签到