C语言学习笔记

306 阅读17分钟

主函数

main函数的返回值类型必须是int,这样返回值才能传递给程序的激活者(如操作系统)。如果main函数的最后没有写return语句的话,C99 规定编译器要自动在生成的目标文件中(如 exe 文件)加入return 0; ,表示程序正常退出。任何其他非零的返回值都有操作系统定义的含义。 通常非零返回值表明有错误出现。每一种操作系统都有自己的方式告诉用户main函数返回什么内容。

变量类型

  • int整型(通常为 16 位,其取值范围在-32768~32767 之间,也有用 32 位表示的int类型)
  • float浮点型(通常是 32 位,它至少有 6 位有效数字,取值范围一般在 10-38~1038之间)
  • char字符——一个字节
  • short短整型
  • long长整型
  • double双精度浮点型

在 C 语言及许多其它语言中,整数除法操作将执行舍位,结果中的任何小数部分都会被舍弃。 但是,如果某个算术运算符有一个浮点型操作数和一个整型操作数,则在开始运算之前整型操作数将会被转换为浮点型。

const

int main() {
	int a = 100;
	int b = 200;
	int *const p1 = &a;//const挨着p,即p不可修改
	const int * p2 = &a;//const挨着*p,即*p不可修改
	const int * const p3 = &a;//都挨着,都不可修改
	//p1 = &b;
	//*p2 = 100;
	
	return 0;
}

类型转换

unsighed类型

unsighed类型只能是正数或0

unsignedint高级,n <= in被隐式类型转换,无符号数负数变为最大值。

在c++中,如果赋值给无符号类型一个超过它表示范围的值(如负数),结果是初始值对无符号类型表示数值最大值取模后的余数

#include<stdio.h>
void test(int n)
{
    for (unsigned int i = 0; n <= i; n--)
    {
        printf("%d",n);
    }
}
int main()
{
	test(10);
    return 0;
}

输出结果

死循环

1.强制类型转换

int a = 2;
int b = 3;
printf("%f",(float)a / b);

2.隐式类型转换

int a = 2;
int b = 3;
printf("%f",(a * 1.0) / b);

符号常量

  • 在程序中使用”幻数”-就是具体的数,反映不出数字所代表的意义-无法向以后阅读该程序的人提供什么信息,使程序的修改变得更加困难。处理这种幻数的一种方法是赋予它们有意义的名字。#define指令可以把符号名(或称为符号常量)定义为一个特定的字符串。

    #define 名字  替换文本
    
  • 程序中出现的所有在#define中定义的名字(既没有用引号引起来,也不是其它名字的一部分)都将用相应的替换文本替换。其中,名字与普通变量名的形式相同:它们都是以字母打头的字母和数字序列;替换文本可以是任何字符序列,而不仅限于数字。

  • 符号常量名通常用大写字母拼写,这样可以很容易与用小写字母拼写的变量名相区别。注意,#define指令行的末尾没有分号。

运算符

位运算符

按位与&
按位或``
按位异或^
按位取反~
左移运算符<<左移1位相当于乘2
右移运算符>>右移1位相当于除2

例题

计算2^0 + 2^1 + 2^2 + 2^3 + 2^4 + ... + 2^n的结果
int main()
{
    int n = 0;
    scanf("%d",&n);
    printf("%d",1 << (n + 1) - 1);
    return 0;
}

&&||!

&&前为0时不运行&&后面的

||前不为0时不运行||后面的

#include<stdio.h>
int main()
{
	int a = 0, b = -1;
	printf("%d %d %d\n", a++ && b++, a, b);
    printf("%d %d %d", a++ || b++, a, b);
    return 0;
}

输出结果

0 1 -1
1 2 -1

三步运算符

a>b成立返回1否则返回-1

return a > b ? 1 : -1

C语言运算符优先级

逻辑语句

switch...case...break

  • switch必须是整型

    case必须是整型常量

  • case分支的值不能相同

  • break不可忽视

  • 如果现在case分支中定义一个变量,加一个括号

  • 所有case分支条件都不满足,那么执行default分支

  • default不是必须的。当没有default时,如果所有case都匹配失败,那么就什么都不执行

#include<stdio.h>
int main() {
    int a;
    scanf("%d",&a);
    switch(a) {
    case 1:
        printf("%d",a);
        break;
    case 2:
    {
        int b = 10;
        printf("%d",b);
        break;
    }
    default:
        printf("输入有误");
        break;
    }
}

goto用法

C语言提供了可以随意滥用的goto语句以及标记跳转位置的标号。可以跳出多重循环。

例子

求200以内的素数

#include<stdio.h>
int main()
{
    int j = 0;
    for (int i = 2; i <= 200; i++)
    {
        int j = 2;
        while (j < i)
        {
            if (i % j == 0) //当i能被除自己和1意外的数字整除时跳出循环。
            {
                goto Tiaoguo;
            }
            j++;
        }
        printf("%d\n",i);
        Tiaoguo:
            continue;
    }
    
    return 0;
}

输出结果

2
3
5
7
11
13
17
19
23
29
31
37
41
43
47
53
59
61
67
71
73
79
83
89
97
101
103
107
109
113
127
131
137
139
149
151
157
163
167
173
179
181
191
193
197
199

指针基础

房子理论

变量的三大要素

1.名字 2.空间 3.地址

两种操作

1.读 2.写

指针变量

p中放的是谁的地址,*p就是谁

int a = 10;
int* p = &a;//指针变量:用来存储a的地址
printf("%d\n",*p);
*p = 20;
printf("%d\n",a);

输出结果

10
20

指针的运算

int main()
{
  int nums[10] = {0, 1, 2, 3, 4, 5};
	int* p = nums;
	p++;//p = p + 1*sizeof(int);
	printf("%d ", sizeof(int));
	printf("%d ", &nums[0]);
	printf("%d ", &nums[1]);
	printf("%d ", &nums[2]);
  return 0;
}

指针变量的大小

32位编译环境下是4个字节 64位编译环境下是8个字节

函数

  • 传入参数,传出参数,传入传出参数:只针对指针类型的形参区分传入、传出。
    • 传入参数:传入的时候需要赋值,并且在函数内部不会对该参数进行修改。传入参数一般前面都有const。
    • 传出参数:传入的时候可以不赋初值,在函数内部不会读取该参数的信息,而是会对该参数进行写操作(赋值)。
    • 传入传出参数:传入的时候需要赋值,并且在函数内部也会对该参数进行写操作(赋值)。

函数指针

与数据项相似,函数也有地址。函数的地址是存储其机器语言代码的内存的开始地址。通常,这些地址对用户而言,既不重要,也没有什么用处,但对程序而言,却很有用。例如,可以编写将另一个函数的地址作为参数的函数。这样第-个函数将能够找到第二个函数,并运行它。与直接调用另一个函数相比,这种方法很笨拙,但它允许在不同的时间传递不同函数的地址,这意味着可以在不同的时间使用不同的函数。

#include<stdio.h>
int f1(float f) {
}
int* f2(char c,int i) {
	printf("f2");
}
int f3(float f) {

}
int f4(float f) {

}
typedef int*(*pFun)(char, int);
int main() {
	//函数指针
	int(*pf)(float) = f1;//与f1,f3,f4匹配
	int*(*pd)(char, int) = f2;
	f2('1', 1);
	(*pd)('1', 1);
	pFun p = &f2;
}

输出结果

f2f2

数组

[]的意思

*(nums + i) <===>nums[i]

数组的名字

代表的是数组第一个元素的地址(首地址)。

#include<stdio.h>
int main() {
	int a[4] = { 1,2,3,4 };
	int *ptr = (int *)(&a + 1); // 1 * 4 * 4
	printf("%d", *(ptr - 1));
	//a 数组首地址
	//&a 数组的地址
}
4

数组传参

数组传参过程中,数组int nums[]会退化成指针int *nums

void fun(int* nums, int n)
{
}
int main()
{
  int nums[] = {1,2,3,4};
  fun(nums,sizeof(nums)/sizeof(int));
}

例题

打乱顺序数组的顺序

#include<stdio.h>
#include<stdlib.h>
#include<time.h>
void printArray (int *arry, int n)
{
    for (int i = 0; i< n; i++){
        printf ("%d ", arry[i]);
    }
}
int main()
{
    int arry[11] = { 1,2,3,4,5,6,7,8,9,10,11};
    srand(time(0));//种随机种子
    for (int i = 0; i < 11; i++)
    {
        int r = rand() % 11;
        int t = arry[i];
        arry[i] = arry[r];
        arry[r] = t;
    }
    printArray(arry, 11) ;
    return 0; 
}

字符串

  • 字符串用""

ASCII表

#include<stdio.h>
int main()
{
  printf("%d","abcdefghi");
  printf("\n");
  printf("%d",*"abcdefghi");
  return 0;
}

输出结果

4210688
97

字符

  • char类型在内存中占1个字节 字符用单引号''

0,'0','\0'

#include<stdio.h>
int main()
{
  char c = '0';
  printf("%c\n",c); //0
  printf("%d\n",c); //48
  c = 0;
  printf("%c\n",c); //
  printf("%d\n",c); //0
  c = '\0';
  printf("%c\n",c); //
  printf("%d\n",c); //0
  return 0;
}

转义字符

#include <stdio.h>
int main ( )
{
  char c = '\'';
  c ='\\';
  c ='\n';
  c = '\t';
  c = '\a';
  printf( "%c", c);
  return 0;
}

""代表的是字符串中首个字符的地址

#include<stdio.h>
int main()
{
  printf("%d\n","abcdefghi");//字符串实际上时第一个字符的地址,即字符串的首地址
  printf("%d\n",*"abcdefghi");//*"abcdefghi"即为a
  return 0;
}

输出结果

4210688
97

为什么使用char*存储字符串

#include <stdio.h>
int main()
{
  char* p = "abcdefghi";
  p++;//p = p + sizeof(char)在遍历字符串的时候指针变量偏移一个字节
  printf("%c", *p);
  return 0;
}

输出结果

b
  • 注意char array[] = {'c','h','i','n','a'};char array[] = "china";不同,第一种数组大小是5,第二种数组大小是6比第一种多一个元素\0

计算字符串长度

  • \012:\0后接八以内数字表示八进制
  • \x12:\x后接数字或abcdef表示十六进制
  • strlen()\0作为字符串结束标志
#include<stdio.h>
#include<string.h>
int main()
{
    char* p = "a\0\n\012ab0";
    printf ("%d ", sizeof(p)) ; //8 64位系统字符串占8个字节
    
    printf ("%d ", sizeof ("a\0\n\012ab0")); //8
    //a 0 \n \012 a b 0 \0
    
    int n = strlen(p);
    printf ("%d ", n); //1
    //a \0 strlen()以\0作为字符串结束标志,其中\0不计入长度
    
    p++;
    p++;
    n=strlen (p) ;
    printf("%d ", n); //5
    // \n \012 a b 0 \0
    
    return 0;
}

输出结果

8 8 1 5 

字符数组和字符串常量的区别

常量

  • 常量不可修改
  1. 整型常量 10 100
  2. 字符常量 'a' 'c'
  3. 浮点常量 1.1 1.01
  4. 字符串常量 "asdfd"
  5. 地址常量 变量的地址 数组名字 函数名字
#include<stdio.h>
int main()
{
    char* p = "a\0\n\012ab0"; //字符串常量
    char b[10] = "a\0\n\012ab0"; //字符数组
    printf("%d ",sizeof(p)); //8
    printf("%d ", sizeof(b)); //10
    //p++;
    //b++; //b为数组名是常量不允许修改,该语句有错
    
    //*p++;
    //*b++; //b++先执行后执行*,产量不允许修改,同上
    
    //(*p)++; //p存储的是字符串中第一个字符的地址,字符串存放在常量区是常量,不允许修改
    //(*b)++;
    return 0;
}

gets函数

  • gets函数只有一个参数。参数类型为char*型,即str可以是一个字符指针变量名,也可以是一个字符数组名。
  • gets()函数的功能是从输入缓冲区中读取一个字符串存储到字符指针变量str所指向的内存空间。
  • gets()函数不仅比scanf简洁,而且,就算输入的字符串中有空格也可以直接输入,不用像scanf那样要定义多个字符数组。
  • gets(str);完全可以取代:scanf("%s", string);
#include<stdio.h>
int main()
{
  char *gets(char *str);
  char a[50];
  gets(a);
  return 0;
}
# include <stdio.h>
int main(void)
{
    char str[20] = "\0";  //字符数组初始化\0
    printf("请输入字符串:");
    gets(str);
    printf("%s\n", str);
    return 0;
}

输出结果

请输入字符串:hello the world
hello the world

字符数组的两种遍历方式

不同点参考上面《字符数组和字符串常量的区别》

#include<stdio.h>
int main()
{
    char str[20] = "";
    gets(str);
    int i = 0;
    while( str[i] != '\0')
    {
        printf("%c",str[i]);
        i++;
    }
    
    char *p = str;
    while ( *p != '\0')
    {
        printf("%c",*p);
        p++;
    }
    
    return 0;
}

字符串函数常见操作

  • 指针变量判空
  • 最后赋值'\0'
  • 返回值(目的地址)以便支持链式操作

例题

1.字符串拷贝

#include<stdio.h>
#include<stdlib.h>
char* m_strcpy(char* des, const char* src)
{
    //1.判断指针变量是否为NULL
    if (des == 0 || src == 0)
    {
        return 0;//结束
    }
    //2.字符串拷贝
    int i = 0;
    for (i = 0; src[i] != '\0'; i++)
    {
        des[i] = src[i];
    }
    //3.赋值'\0'
    des[i] = '\0';
    //4.返回值目的地址
    return des; //支持链式操作
}
int main()
{
    char src[100] = "";
  	gets(src);
    char des[100] = "";
    //strcpy(des,src);
    m_strcpy(des, src);
    printf("%s", des);
    return 0;
}

内存管理

常量区

特点:

  • 军事管理区,不可修改

  • 常量区空间没有名字

  1. 整型常量 10 20 -10

  2. 浮点常量 1.2 1.001

  3. 字符常量 'a' '0' '\0'

  4. 字符串常量 "adjkls" "56as"

  5. 地址常量 int a; &a 数组名 函数名

栈区

特点:

  1. 租的房子,房子到期自动回收
  2. 访问速度快
  3. 空间少
  4. 作用域和生命周期从定义的位置开始到'}'
  5. 常用于局部变量、函数参数

全局区

全局变量

特点

  1. 局部大于全局(当局部变量和全局变量重名时局部变量优先)
  2. 初始化默认为0
  3. 生命周期为程序开始到程序结束
  4. 作用域为整个项目
  5. 引用其他文件中的全局变量
/mian.c/
#include<stdio.h>
int g_value;
int main()
{
    printf("%d",g_value);
    return 0;
}
/*a.c*/
extern int g_value;
void print()
{
    g_value = 100;
    printf("%d",g_value);
}

静态区

静态局部变量

  1. 生命周期从程序开始到程序结束
  2. 作用域到'}'
  3. 函数结束不释放※
  4. 只被初始化一次※
#include<stdio.h>

int fun()
{
    int a = 10;
    static int s = 10;
    printf("%d ", a++);
    printf("%d ", s++);
}
int main()
{
    fun();//10 10
    fun();//10 11
    return 0;
}

静态全局变量

  1. 生命周期从程序开始到程序结束
  2. 作用域为当前文件

堆区

特点

  1. 买的房子,不会自动释放
  2. 不会自动释放
  3. 容量大
  4. 访问速度慢
  5. 堆区空间没有名字
  6. 用完需要释放
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main()
{
    char* p = malloc(400);//在堆区申请空间(买房子)
    //返回值为地址,不一定是char,但一定是*
    //char* 房子被分为400份,一份1个字节
    //int* 房子被分为100份,一份4个字节
    strcpy(p,"123456");
    //房子不用是记得释放
    free(p);
    return 0;
}

注意

  1. 不要访问越界
  2. 不要忘记释放空间

GetMemory笔试题

Test1

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void GetMemory1(char *p) {
	p = (char *)malloc(100);//强制类型转换
}
void Test1() {
    char *str = NULL;
    GetMemory1(str);
    strcpy(str, "hello world");
    printf(str);
}
int main() {
	Test1();
	return 0;
}
  • 访问空指针,异常退出
解决办法1
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
char *GetMemory1(char *p) {
	p = (char *)malloc(100);
    return p;
}
void Test1(void) {
    char *str = NULL;
    str = GetMemory1(str);
    strcpy(str, "hello world");
    printf(str);
    free(str);
}
int main() {
	Test1();
	return 0;
}
解决办法2
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void GetMemory1(char **p) { //二级指针变量**p
	  *p = (char *)malloc(100);//强制类型转换
}//p存放的是str的地址
void Test1() {
    char *str = NULL;
    GetMemory1(&str);
    strcpy(str, "hello world");
    printf(str);
    free(str);
}
int main() {
	  Test1();
	  return 0;
}

Test2

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
char *GetMemory2(void) {
    char p[] = "hello world";
    return p;
}
void Test2(void) {
    char *str = NULL;
    str = GetMemory2();
    printf(str);
}
int main() {
    Test2();
	  return 0;
}
  • 字符数组在栈区分配,函数结束被释放

Test3

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
char *GetMemory3 (void) {
    char *p = "hello world";//字符串是常量,不会改变 故虽然p被释放但常量空间不释放
    return p;
}
void Test3 (void) {
    char *str = NULL;
    str = GetMemory3();
    printf(str);
}
int main() {
    Test3();
	  return 0;
}

输出结果

hello world

Test4

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
char *GetMemory4 (void) {
    static char p[] = "hello world";
    return p;
}
void Test4 (void) {
    char *str = NULL;
    str = GetMemory4();
    printf(str);
}
int main() {
    Test4();
	return 0;
}

输出结果

hello world
  • 静态局部变量不会被释放空间,生命周期是全局的

Test5

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void Test5 (void) {
    char *str = (char *)malloc(400);
    strcpy(str, "hello");
    free(str);
    //str = NULL;
    if (str != NULL)
    {
        strcpy(str, "world");
        printf(str);
    }
}
int main() {
    Test5();
	return 0;
}

输出结果

world
  • strfree后变为野指针,空间被释放,但仍存储着房子的地址
  • free是剥夺变量房子的使用权,但变量仍记得房子地址
  • 平时记得买完房子free记得置空str = NULL;
  • 内存已经被释放,访问野指针造成内存泄漏
  • 指针在free后未赋值 NULL,便会使人以为是合法的。它们只是把指针所指的内存给释放掉,但并没有把指针本身干掉。此时指针指向的就是“垃圾”内存。释放后的指针应立即将指针置为NULL,防止产生“野指针”。※

Test6

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void Test6 (void) {
    char *str = (char *)malloc(400);
    strcpy(str, "hello");
    str += 6;
    free(str);
    
    if (str != NULL)
    {
        strcpy(str, "world");
        printf(str);
    }
}
int main() {
    Test6();
	return 0;
}
  • str += 6;后地址改变free卖的是别人的房子
  • 出现异常,释放空间不对。

结构体

结构体定义

#include<stdio.h>
#include<stdlib.h>
struct STUDENT
{
	int number;
	int age;
	int weight;
	char s;
}student; //student为全局变量
typedef struct STUDENT1
{
	char a;
	int weight;
	char s;
}student1; //student1等于STUDENT1
int main()
{
    student.age = 10;
	struct STUDENT abc = {1001, 22, 110, 'M'}; //初始化结构体
	student1 bcd = {1001, 22, 110, 'M'}; //等于struct student bcd = {1001, 22, 110, 'M'};
    //通常用typedef sturct,方便
    printf("%d %d %d %c ",abc.number,abc.age,abc.weight,abc.s);
    return 0;
}

输出结果

1001 22 110 M

字节对齐

  • 按照最长的成员对齐
  • 保证整数倍地址对齐(比如:short占两个字节不能放在奇数地址,char随便放奇偶都可以)
typedef struct STUDENT
{
	int a;
	char c;
}student;
typedef struct STUDENT
{
	char s;
	int a;
	char c;
}student;
typedef struct STUDENT
{
	int a;
	char s;
	char c;
    short d; //short占两个字节
}student;
typedef struct STUDENT
{
	int a;
	char s;
	short d;
	char c;
}student;
typedef struct STUDENT
{
	int a;
	long long c; //long long占八个字节
	char d;
	int b;
	short s;
}student;
typedef struct STUDENT
{
	char a : 2;//2代表2比特bt,一个字节8bt
	char b : 2;
	char c : 2;
	char d : 2;
}student;

  • 结构体定义的原则,保证结构体字节对齐
# pragma pack(2)//控制结构体按2字节对齐
typedef struct STUDENT
{
	int a;
	char c;
	char res[3];//跨平台时由于编译器不同,需强制对齐
}student;
#pragma pack()

结构体数组

p->score <===> (*p).score

#include<stdio.h>
#include<stdlib.h>
typedef struct STUDENT
{
	int number;
	int score;
}student;
int main()
{
    int array[10];
    char buffer[10];
    student s[10] = {{1,10},{2,30}};
    printf("%d",s[0].number);
    student* p = s;
    printf("%d ",s[0].score);
    printf("%d ",(*p).score);
    printf("%d",p->score); //p->score <===> (*p).score
    return 0;
}

输出结果

1 10 10 10

链表

特点

  • 空间不连续
  • 不支持随机访问
  • 插入删除的效率高

创建链表

  • head->next要初始化为空,因为链表以节点next域为空判断链表是否接受
  • head在栈区,malloc在堆区分配一片空间给head,head存放的是这片空间的地址

带头结点的头插法

#include<stdio.h>
#include<stdlib.h>
typedef struct NODE
{
    int num;
    struct NODE* next;//不能用Node,因为编译顺序Node在这句话之后
}Node;
int main()
{
    Node *head = (Node *)malloc(sizeof(Node));
    //head->next要初始化为空,因为链表以节点next域为空判断链表是否结束
    head->next = NULL;
    //head在栈区,malloc在堆区分配一片空间给head,
    //head存放的是这片空间的地址
    int number;
    while (1)
    {
        scanf("%d",&number);
        if (number <= 0)
        {
            break;
        }
        Node *p = (Node *)malloc(sizeof(Node));
        p->num = number;
        p->next = head->next;
        head->next = p;
    }
    
    return 0;
}

打印链表

void printLink(Node* p){
    while (p) // <===> p != NULL
    {
        printf("%d ",p->num);
        p = p->next;
        //不能用p++,因为链表空间不是连续的
    }
}

释放链表

void freeLink(Node* p){
    Node *q = NULL;
    while (p)
    {
        q = p;
        p = p->next;
        free(q);
    }
}

查找节点

Node* searchLink(Node* p, int num){
    while (p)
    {
        if(p->num == num)
        {
        	return p;
        }
        p = p->next;
    }
    return NULL;
}

修改节点

Node* modifyLink(Node* p, int num){
    while (p)
    {
        if(p->num == num)
        {
        	p->num = 20;
        	return p;
        }
        p = p->next;
    }
    return NULL;
}

删除节点

int modifyLink(Node* p, int num){
    if(p == NULL)
        return;
	//默认带头节点
    Node *q = p;
    p = p->next;
    while (p)
    {
        if(p->num == num)
        {
        	q->next = p->next;
        	free(p);
        	return 0;
        }
        q = p;
        p = p->next;
    }
    return 1;
}

插入节点

在findnum前插入一个num = newnum的节点

int modifyLink(Node* p, int findnum, int newnum){
    if(p == NULL)
        return;
	//默认带头节点
    Node *q = p;
    p = p->next;
    while (p)
    {
        if(p->num == num)
        {
            Node *r = (Node *)malloc(sizeof(Node));
            r->num = findnum;
        	q->next = r;
            r->next = p;
        	return 0;
        }
        q = p;
        p = p->next;
    }
    return 1;
}

联合

  1. 所有成员共享一块空间
  2. sizeof计算成员中字节最大的空间大小
  3. 各成员内存重叠
  4. 在使用时和结构体语法相同

优势

  • 省空间
#include<stdio.h>
union UN
{
    int a;
    int c;
};
int main()
{
    printf("%d\n",sizeof(union UN));
    union UN u;
    u.a = 10;
    printf("%d %d\n",u.a,u.c);
    return 0;
}

输出结果

4
10 10

#include<stdio.h>
union UN
{
    int a;
    char c;
};
int main()
{
    printf("%d\n",sizeof(union UN));
    union UN u;
    u.c = 10;
    printf("%d %d",u.a,u.c); //a没有被赋值
    return 0;
}

输出结果

4
10 10

大小端

小端存储

高地址(数大的)放高位(如例子中的万位),低地址放低位

十进制二进制
1000000000 0000 0000 0001 1000 0110 1010 0000
0x12340x12350x12360x1237
1010 00001000 01100000 00010000 0000

大端存储

低地址(数小的)放高位(如例子中的个位),高地址放低位

十进制二进制
1000000000 0000 0000 0001 1000 0110 1010 0000
0x12340x12350x12360x1237
0000 00000000 00011000 01101010 0000

例题:

判断电脑cpu是什么架构,是大端存储还是小端存储

常见电脑都是小端存储

#include<stdio.h>
int checkSystem()
{
    union UN
    {
        int a;
        char c;
    }u;
    u.a = 1;
    return u.c == 1;
}
int main()
{
    printf("%d",checkSystem());
    return 0;
}
若是大端存储
十进制二进制
10000 0000 0000 0000 0000 0000 0000 0001
0x12340x12350x12360x1237
0000 00000000 00000000 00000000 0001

输出结果

0
若是小端存储
十进制二进制
10000 0000 0000 0000 0000 0000 0000 0001
0x12340x12350x12360x1237
0000 00010000 00000000 00000000 0000

输出结果

1

枚举

用来屏蔽魔鬼数字,用来表示某种含义

#include<stdio.h>

//默认从0开始
enum ENUM {
    SUN,
    MON = 10,
    TUE,
    WED,
    THU,
    FRI,
    SAT
};

int main()
{
    int a = TUE;
    printf("%d ",SUN);
    printf("%d",a);
    return 0;
}

输出结果

0 11
int state;//存储一个状态
//线程:拥有很多状态,启动,运行,阻塞,终止
//游戏人物:静止,移动,攻击前摇,攻击后摇。
enum hero { stop, move, attack_f, attack_e };
int main() {
	hero state;
    state = stop;
    cout << state << endl;
    state = move;
    cout << state << endll
    cout << sizeof(state) << endl;
}

宏定义

用来屏蔽魔鬼数字

#include<stdio.h>
#define NUM 10

int main()
{
    int arry[NUM];
    for (int i = 0; i < NUM; i++)
    {
        arry[i] = 10;
    }
    for (int i = 0; i < NUM; i++)
    {
        printf("%d ",arry[i]);
    }
    
    return 0;
}

输出结果

10 10 10 10 10 10 10 10 10 10 

宏定义只是单纯的字符串替换

#include<stdio.h>
#define NUM 5+2

int main()
{
    printf("%d",NUM * 2);
    return 0;
}

输出结果

9

宏函数

#include<stdio.h>
#define MAX(a,b) if (a > b) {printf("%d",a);}else{printf("%d",b);}

int main()
{
    MAX(20,30) //这句加不加;都不行。
    return 0;
}

输出结果

30

宏只能在一行,如果换行要在最后加\

#include<stdio.h>
#define MAX(a,b)\
if (a > b) {\
    printf("%d",a);\
    }\
else{\
    printf("%d",b);\
    }

int main()
{
    MAX(20,30)
    return 0;
}

多文件编程

  • 联合,枚举,宏定义,函数的声明放在.h文件中

  • .c文件要使用需要#include"filename.h"

注意

  1. 不能循环包含头文件

  2. 开头要加#pragma once,防止重复包含头文件或加

    #ifndef __INC_FILENAME_
    #define __INC_FILENAME_
    //代码
    #endif
    

    eg

    #ifndef __INC_222_
    #define __INC_222_
    typedef struct NODE
    {
        int num;
        struct NODE* next;
    };
    #endif
    
    #ifndef __INC_222_
    #define __INC_222_
    typedef struct NODE
    {
        int num;
        struct NODE* next;
    };
    #endif
    

文件读写

#include<stdio.h>

int main()
{
    //1.打开文件
    FILE* fr = fopen("E:\\文档\\新建文本文档.c","r");
    FILE* fw = fopen("E:\\文档\\新建文本文档.c","w");
    if(fr == NULL || fw == NULL)
    {
        printf("文件打开失败");
        return 0;
    }
    //2.读写文件
    char buffer[128];
    fgets(buffer,128,fr);
    printf("%s",buffer);
    
    fputs(buffer,fw);
    //3.关闭文件
    fclose(fr);
    return 0;
}