C语言热身——预处理指令、变量类型、static和extern、结构体、枚举

本文主要是介绍C语言热身——预处理指令、变量类型、static和extern、结构体、枚举,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

预处理指令

预处理指令简介

  1. C语言在对源程序进行编译之前,会先对一些特殊的预处理指令作解释(比如之前使用的#include文件包含指令),产生一个新的源程序(这个过程称为编译预处理),之后再进行通常的编译。
  2. 为了区分预处理指令和一般的C语句,所有预处理指令都以符号”#”开头,并且结尾不用分号。
  3. 预处理指令可以出现在程序的任何位置,它的作用范围是从它出现的位置到文件尾。习惯上我们尽可能将预处理指令写在源程序开头,这种情况下,它的作用范围就是整个源程序文件。
  4. C语言提供的预处理指令主要有:宏定义、文件包含、条件编译。

不带参数的宏定义

#define 宏名 字符串

//NUM代表宏名
//6是用来替换宏名的字符串
#define NUM 6int main()
{int arr[NUM] = {1,2,3,4,5,6};for(int i = 0; i < NUM; i++){printf("arr[%d] = %d", i, arr[i]);}return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

它的作用是在编译预处理时,将源程序中所有”宏名”替换成右边的”字符串”,常用来定义常量。

带参数的宏定义

#define 宏名(参数列表) 字符串

#define sum(a, b) ((a)+(b))int main()
{int a = sum(10, 20);printf("%d", a);return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

使用注意:

1.  宏名一般用大写字母,以便与变量名区别开来,但用小写也没有语法错误。
2.  对程序中用双引号扩起来的字符串内的字符,不进行宏的替换操作。
3.  在编译预处理用字符串替换宏名时,不作语法检查,只是简单的字符串替换。只有在编译的时候才对已经展开宏名的源程序进行语法检查。
4.  宏名的有效范围是从定义位置到文件结束。如果需要终止宏定义的作用域,可以用 #undef 命令。
5.  定义一个宏时可以引用已经定义的宏名。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

注意:宏定义纯粹是字符串替换,不会做计算!

带参数宏定义与函数的区别

  1. 宏定义不涉及存储空间的分配、参数类型匹配、参数传递、返回值问题。
  2. 函数调用在程序运行时执行,而宏替换只在编译预处理阶段进行。所以带参数的宏比函数具有更高的执行效率。

条件编译

在很多情况下,我们希望程序的其中一部分代码只有在满足一定条件时才进行编译,否则不参与编译(只有参与编译的代码最终才能被执行),这就是条件编译

语法

#if 条件1
...code1...
#elif 条件2
...code2...
#else 条件3
...code3...
#endif 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  1. 如果条件1成立,那么编译器就会把#if 与 #elif之间的code1代码编译进去。
  2. 如果条件1不成立、条件2成立,那么编译器就会把#elif 与 #else之间的code2代码编译进去。
  3. 如果条件1、2都不成立,那么编译器就会把#else 与 #endif之间的code3编译进去。
  4. 注意,条件编译结束后,要在最后面加一个#endif。
  5. #if 和 #elif后面的条件一般是判断宏定义而不是判断变量,因为条件编译是在编译之前做的判断,宏定义也是编译之前定义的,而变量是在运行时才产生的、才有使用的意义。

文件包含

#include,它可以将一个文件的全部内容拷贝另一个文件中。 
1. #include <文件名>:直接到C语言库函数头文件所在的目录中寻找文件。 
2. #include “文件名”:系统会先在源程序当前目录下寻找,若找不到,再到操作系统的path路径中查找,最后才到C语言库函数头文件所在目录中查找。

使用注意

1.  #include指令允许嵌套包含,比如a.h包含b.h,b.h包含c.h,但是不允许递归包含,比如 a.h 包含 b.h,b.h 包含 a.h。
2.  使用#include指令可能导致多次包含同一个头文件,降低编译效率。使用条件编译来避免重复包含。
  • 1
  • 2
  • 3

变量类型

变量的作用域

局部变量:在函数内部定义的变量,称为局部变量。形参也是局部变量。只在定义它的函数内部有效。 
全局变量:在函数外部定义的变量,称为全局变量。作用范围是从定义代码的位置开始到源程序结束。

变量的存储类型

就是指变量存储在什么地方(普通内存、运行时堆栈、硬件寄存器)。 
变量的存储类型决定了变量何时创建、何时销毁以及的它的值能保存多久,也就是决定了变量的生命周期。 
根据的变量存储类型的不同可以把变量分为:自动变量、静态变量、寄存器变量。

自动变量

  1. 自动变了是存储在堆栈中的。
  2. 所有的局部变量在默认情况下都是自动变量。
  3. 生命周期:在程序执行到声明自动变量的代码块(函数)时,自动变量才被创建;当自动变量所在的代码块(函数)执行完毕后,这些自动变量就会自行销毁。如果一个函数被重复调用,这些自动变量每次都会重新创建。

静态变量

  1. 静态变量是存储在静态内存中的,也就是不属于堆栈。
  2. 所有的全局变量都是静态变量;被关键字static修饰的局部变量也是静态变量。
  3. 生命周期:静态变量在程序运行之前创建,在程序的整个运行期间始终存在,直到程序结束。

寄存器变量

  1. 存储在硬件寄存器中的变量,称为寄存器变量。寄存器变量比存储在内存中的变量访问效率更高(默认情况下,自动变量和静态变量都是放在内存中的)。
  2. 被关键字register修饰的自动变量都是寄存器变量;只有自动变量才可以是寄存器变量,全局变量和静态局部变量不行;寄存器变量只限于int、char和指针类型变量使用
  3. 生命周期:因为寄存器变量本身就是自动变量,所以函数中的寄存器变量在调用该函数时占用寄存器中存放的值,当函数结束时释放寄存器,变量消失。

    注意:1. 由于计算机中寄存器数目有限,不能使用太多的寄存器变量。如果寄存器使用饱和时,程序将寄存器变量自动转换为自动变量处理。2. 为了提高运算速度,一般会将一些频繁使用的自动变量定义为寄存器变量,这样程序尽可能地为它分配寄存器存放,而不用内存。
    
    • 1
    • 2
    • 3
    • 4

static和extern

外部函数:如果在当前文件中定义的函数允许其他文件访问、调用,就称为外部函数。C语言规定,不允许有同名的外部函数。 
内部函数:如果在当前文件中定义的函数不允许其他文件访问、调用,只能在内部使用,就称为内部函数。C语言规定不同的源文件可以有同名的内部函数,并且互不干扰。

extern与函数

//完整的声明一个外部函数需要extern关键字,表示引用一个外部函数
//可以省略extern
extern void test();//完整的定义一个外部函数需要extern关键字
//默认情况下就是外部函数,可以省略extern
extern void test()
{printf("Hello");
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

static与函数

//提前声明一个内部函数
static void test();//内部函数,需要用static关键字修饰,说明不能在其他文件中访问
static void test()
{printf("Hello");
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

小结

  1. 在定义函数时,在函数的最左边加上static可以把该函数声明为内部函数(又叫静态函数),这样该函数就只能在其定义所在的文件中使用。如果在不同的文件中有同名的内部函数,则互不干扰。
  2. static也可以用来声明一个内部函数
  3. 在定义函数时,如果在函数的最左边加上关键字extern,则表示此函数是外部函数,可供其他文件调用。C语言规定,如果在定义函数时省略extern,则为外部函数。
  4. 在一个文件中要调用其他文件中的外部函数,则需要在当前文件中用extern声明该外部函数,然后就可以使用,这里的extern也可以省略。

extern与变量

默认情况下,一个函数不可以访问在它后面定义的全局变量。 
为了解决这个问题,有两种解决办法: 
1. 将变量a定义在main函数的前面。 
2. 用extern在main函数前面对变量a进行提前声明

//完整声明全局变量a,extern可以省略
extern int a;int main()
{a = 10;return 0;
}
//定义变量a
int a;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

重复定义同一个全局变量时,它们代表的都是同一个变量。

int a;
int a;
int a;int main()
{a = 10;return 0;
}
int a;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

可以将全局变量a声明为局部变量后再使用。

int a;int main()
{//代表从外部引用全局变量a,这里的a依然是全局变量,不是局部变量extern int a;a = 10;return 0;
}
int a;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

假如在另一个源文件中也有全局变量int a,那么这两个源文件的所有全局变量int a,都代表着同一个变量。

static与变量

用static修饰的全局变量,可以称为内部变量。

小结

1. extern可以用来声明一个全局变量,但是不能用来定义变量。
2. 默认情况下,一个全局变量是可以供多个源文件共享的,也就说,多个源文件中同名的全局变量都代表着同一个变量。
3. 如果在定义全局变量的时候加上static关键字,此时static的作用在于限制该全局变量的作用域,只能在定义该全局变量的文件中才能使用,跟其他源文件中的同名变量互不干扰。
  • 1
  • 2
  • 3
  • 4

结构体

//定义一个名为student的结构体
struct Student{char* name;int age;
};int main()
{//定义了一个结构体变量stu1struct Student stu1 = {"aaa",20};//"."称为成员运算符,它在所有运算符中优先级最高//访问stu1的age成员stu1.age = "bbb";//将stu1直接赋值给stu2struct Student stu2 = stu1;return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  1. 结构体内部的元素,也就是组成成分,一般称为“成员”。
  2. 结构体变量占用的内存空间是其成员所占内存之和,而且各成员在内存中按定义的顺序依次排列
  3. 访问结构体成员的一般形式为: 结构体变量名.成员名
  4. 结构体内可以包含结构体,但不允许递归包含
  5. 如果某个成员也是结构体变量,可以连续使用成员运算符“.”访问最低一级成员。 
    相同类型的结构体变量之间可以进行整体赋值。
  6. 将结构体变量作为函数参数进行传递时,其实传递的是全部成员的值,也就是将实参中成员的值一一赋值给对应的形参成员。因此,形参的改变不会影响到实参。

指向结构体的指针

//定义一个名为student的结构体
struct Student{char* name;int age;
};int main()
{//定义了一个结构体变量stu1struct Student stu1 = {"aaa",20};// 定义一个指向结构体的指针变量struct Student *p;// 指向结构体变量stu1p = &stu1;//访问结构体的成员// 方式一:结构体变量名.成员名printf("name=%s, age = %d \n", stu1.name, stu1.age);// 方式2:(*指针变量名).成员名printf("name=%s, age = %d \n", (*p).name, (*p).age);// 方式3:指针变量名->成员名printf("name=%s, age = %d \n", p->name, p->age);return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

枚举

枚举是C语言中的一种基本数据类型,并不是构造类型,它可以用于声明一组常数。当一个变量有几个固定的可能取值时,可以将这个变量定义为枚举类型。比如,你可以用一个枚举类型的变量来表示季节,因为季节只有4种可能的取值:春天、夏天、秋天、冬天。

//声明了Season这种枚举类型,它有四个取值{spring, summer, autumn, winter};
enum Season {spring, summer, autumn, winter}; //定义了Season枚举变量s
enum Season s = spring;
s = 2;  //等价于 s = autumn;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  1. C语言编译器会将枚举元素(spring、summer等)作为整型常量处理,称为枚举常量
  2. 枚举元素的值取决于定义时各枚举元素排列的先后顺序。默认情况下,第一个枚举元素的值为0,第二个为1,依次顺序加1。
  3. 可以给枚举变量赋枚举常量或者整型值。
  4. 可以在定义枚举类型时改变枚举元素的值。没有指定值的枚举元素,其值为前一元素加1。
enum Season {spring = 1, summer, autumn, winter}; 
  • 1

typedef

可以使用typedef关键字为各种数据类型定义一个新名字(别名)。

typedef int Integer;
typedef float Float;int main()
{Integer a = 10;Float f = 2.2f;return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  1. 给类型起别名后,原来的类型名还是可以正常使用的。
  2. 可以在别名的基础上再起一个别名。

typedef也可以给指针起别名

typedef char *String;int main()
{//相当于char *str = "This is a string!";String str = "This is a string!";return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

使用typedef给结构体起别名

//定义一个结构体并起别名
typedef struct MyStudent{char* name;int age;
}Student;int main()
{//相当于struct MyStudent stu;Student stu;return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

typedef与指向结构体的指针

//定义一个结构体并起别名
typedef struct MyStudent{char* name;int age;
}Student;typedef Student *SP;int main()
{//相当于struct MyStudent stu;Student stu = {"aaa", 20};// 定义指针变量SP sp = &stu;//利用指针变量访问结构体成员printf("name=%s,age=%d", sp->name, sp->age);return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

typedef与枚举类型

//定义枚举类型,并且起别名
typedef enum season{spring, summer, autumn, winter} Season;
  • 1
  • 2

typedef与指向函数的指针

int sum(int a, int b) 
{int c = a + b;printf("%d + %d = %d", a, b, c);return c;
}typedef int (*MySum)(int, int);int main()
{//定义一个指向sum函数的指针变量pMySum p = sum;//利用指针变量p调用sum函数(*p)(4, 5);return 0;
}

这篇关于C语言热身——预处理指令、变量类型、static和extern、结构体、枚举的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/760562

相关文章

这15个Vue指令,让你的项目开发爽到爆

1. V-Hotkey 仓库地址: github.com/Dafrok/v-ho… Demo: 戳这里 https://dafrok.github.io/v-hotkey 安装: npm install --save v-hotkey 这个指令可以给组件绑定一个或多个快捷键。你想要通过按下 Escape 键后隐藏某个组件,按住 Control 和回车键再显示它吗?小菜一碟: <template

变量与命名

引言         在前两个课时中,我们已经了解了 Python 程序的基本结构,学习了如何正确地使用缩进来组织代码,并且知道了注释的重要性。现在我们将进一步深入到 Python 编程的核心——变量与命名。变量是我们存储数据的主要方式,而合理的命名则有助于提高代码的可读性和可维护性。 变量的概念与使用         在 Python 中,变量是一种用来存储数据值的标识符。创建变量很简单,

usaco 1.3 Mixing Milk (结构体排序 qsort) and hdu 2020(sort)

到了这题学会了结构体排序 于是回去修改了 1.2 milking cows 的算法~ 结构体排序核心: 1.结构体定义 struct Milk{int price;int milks;}milk[5000]; 2.自定义的比较函数,若返回值为正,qsort 函数判定a>b ;为负,a<b;为0,a==b; int milkcmp(const void *va,c

零基础学习Redis(10) -- zset类型命令使用

zset是有序集合,内部除了存储元素外,还会存储一个score,存储在zset中的元素会按照score的大小升序排列,不同元素的score可以重复,score相同的元素会按照元素的字典序排列。 1. zset常用命令 1.1 zadd  zadd key [NX | XX] [GT | LT]   [CH] [INCR] score member [score member ...]

科研绘图系列:R语言扩展物种堆积图(Extended Stacked Barplot)

介绍 R语言的扩展物种堆积图是一种数据可视化工具,它不仅展示了物种的堆积结果,还整合了不同样本分组之间的差异性分析结果。这种图形表示方法能够直观地比较不同物种在各个分组中的显著性差异,为研究者提供了一种有效的数据解读方式。 加载R包 knitr::opts_chunk$set(warning = F, message = F)library(tidyverse)library(phyl

透彻!驯服大型语言模型(LLMs)的五种方法,及具体方法选择思路

引言 随着时间的发展,大型语言模型不再停留在演示阶段而是逐步面向生产系统的应用,随着人们期望的不断增加,目标也发生了巨大的变化。在短短的几个月的时间里,人们对大模型的认识已经从对其zero-shot能力感到惊讶,转变为考虑改进模型质量、提高模型可用性。 「大语言模型(LLMs)其实就是利用高容量的模型架构(例如Transformer)对海量的、多种多样的数据分布进行建模得到,它包含了大量的先验

hdu 2489 (dfs枚举 + prim)

题意: 对于一棵顶点和边都有权值的树,使用下面的等式来计算Ratio 给定一个n 个顶点的完全图及它所有顶点和边的权值,找到一个该图含有m 个顶点的子图,并且让这个子图的Ratio 值在所有m 个顶点的树中最小。 解析: 因为数据量不大,先用dfs枚举搭配出m个子节点,算出点和,然后套个prim算出边和,每次比较大小即可。 dfs没有写好,A的老泪纵横。 错在把index在d

native和static native区别

本文基于Hello JNI  如有疑惑,请看之前几篇文章。 native 与 static native java中 public native String helloJni();public native static String helloJniStatic();1212 JNI中 JNIEXPORT jstring JNICALL Java_com_test_g

自定义类型:结构体(续)

目录 一. 结构体的内存对齐 1.1 为什么存在内存对齐? 1.2 修改默认对齐数 二. 结构体传参 三. 结构体实现位段 一. 结构体的内存对齐 在前面的文章里我们已经讲过一部分的内存对齐的知识,并举出了两个例子,我们再举出两个例子继续说明: struct S3{double a;int b;char c;};int mian(){printf("%zd\n",s

【编程底层思考】垃圾收集机制,GC算法,垃圾收集器类型概述

Java的垃圾收集(Garbage Collection,GC)机制是Java语言的一大特色,它负责自动管理内存的回收,释放不再使用的对象所占用的内存。以下是对Java垃圾收集机制的详细介绍: 一、垃圾收集机制概述: 对象存活判断:垃圾收集器定期检查堆内存中的对象,判断哪些对象是“垃圾”,即不再被任何引用链直接或间接引用的对象。内存回收:将判断为垃圾的对象占用的内存进行回收,以便重新使用。