YaoCheng8667 的个人博客 YaoCheng8667 的个人博客

记录精彩的程序人生

目录
C语言学习笔记
/  

C语言学习笔记

1. C 存储类

1.1 定义

存储类定义 C 程序中变量/函数的范围(可见性)和生命周期。这些说明符放置在它们所修饰的类型之前。下面列出 C 程序中可用的存储类:

  • auto
  • register
  • static
  • extern

1.2 auto 存储类

auto 存储类是局部变量默认的存储类,只可以在函数内,只可修饰局部变量。

void func(){
	int a;
	auto int b;                    //a,b具有相同的存储类
}

1.3 register 存储类

register 存储类用于定义存储在寄存器中而不是 RAM 中的局部变量。这意味着变量的最大尺寸等于寄存器的大小(通常是一个词),且不能对它应用一元的 '&' 运算符(因为它没有内存位置)。

{
	register int miles;
}

定义 'register' 并不意味着变量将被存储在寄存器中,它意味着变量可能存储在寄存器中,这取决于硬件和实现的限制。

1.4 static 存储类

static 存储类指示编译器在程序的生命周期内保持局部变量的存在,而不需要在每次它进入和离开作用域时进行创建和销毁。因此,使用 static 修饰局部变量可以在函数调用之间保持局部变量的值。

static 修饰符也可以应用于全局变量。当 static 修饰全局变量时,会使变量的作用域限制在声明它的文件内。对于全局变量而言 static 是默认的。

void func1(){
	static int a = 5;
	a++;
	printf("a is %d \n",a);
}

int main(){
	int i;
	for(i = 0;i < 4;i++)
		func1();
}

上述代码将输出:

a is 5
a is 6
a is 7
a is 8

1.5 extern 存储类

extern 存储类用于提供一个全局变量的引用,全局变量对所有的程序文件都是可见的。当您使用 extern 时,对于无法初始化的变量,会把变量名指向一个之前定义过的存储位置。

c++ 中称为变量或函数的声明,当函数或变量定义为 extern 时,若本文件中没有该变量或函数的初始化或定义,会在别的文件中寻找其定义或初始化。

extern 修饰符通常用于当有两个或多个文件共享相同的全局变量或函数的时候,如下所示:

第一个文件:main.c

#include <stdio.h>

int count ;
extern void write_extern();

int main(){
	count = 5;
	write_extern();
}

第二个文件:support.c

#include <stdio.h>

extern int count;

void write_extern(void){
	printf("count is %d \n",count);
}

上述代码输出为:

count is 5

2. C 枚举

2.1 基本介绍

枚举是 C 语言中的一种基本数据类型,它可以让数据更简洁,更易读。
枚举语法定义格式为:

enum 枚举名 {枚举元素1,枚举元素2,... ...};

例如:

enum DAY{
	MON=1, TUE , WED , THU , FRI , SAT , SUN
};

注意: 第一个枚举成员的默认值为整型的 0,后续枚举成员的值在前一个成员上加 1。我们在这个实例中把第一个枚举成员的值定义为 1,第二个就为 2,以此类推。

可以在定义枚举类型时改变枚举元素的值:

enum season{
	spring,summer = 3,autumn,winter;
}
/*
spring = 0  ,  summer = 3  ,  autumn = 4  ,  winter = 5
*/

2.2 枚举变量的定义

有三种方式:

  1. 先声明枚举类型,再定义枚举变量。
enum DAY{
	MON=1, TUE , WED , THU , FRI , SAT , SUN
};
enum DAY day;
  1. 声明枚举类型的同时定义枚举变量。
enum DAY{
	MON=1, TUE , WED , THU , FRI , SAT , SUN
} day;
  1. 省略枚举名称,直接定义枚举变量。
enum {
	MON=1, TUE , WED , THU , FRI , SAT , SUN
} day;

2.3 枚举的使用

2.3.1 枚举用于遍历

#include <stdio.h>

enum DAY{
	MON=1, TUE , WED , THU , FRI , SAT , SUN
} day;
int main(){
	for(day = MON ; day<=SUN ; day++){
		printf("%d",day);
	}
}

2.3.2 枚举用于 switch

#include <stdio.h>
#include <stdlib.h>
int main(){
	enum color { red = 1, green , blue};
	enum color favorite_color;
    	printf("请输入你喜欢的颜色: (1. red, 2. green, 3. blue): ");

    	scanf("%d", &favorite_color);
	
	switch(favorite_color){
		case red:
			printf("red \n");
			break;
		case green:
			printf("green \n");
			break;
		case blue:
			printf("blue \n");
			break;
		default:
			printf("none \n");
	}
	return 0;
}

2.3.3 将整数转换为枚举

enum DAY{
	MON=1, TUE , WED , THU , FRI , SAT , SUN
} day;
int main(){
	int a = 1;
	enum day weekday;
	weekday = ( enum day ) a;
	// weekday = a; // 错误
	return 0;
}

3. 共用体

共用体是一种特殊的数据类型,允许您在相同的内存位置存储不同的数据类型。可以定义一个带有多成员的共用体,但是任何时候只能有一个成员带有值。共用体提供了一种使用相同的内存位置的有效方式。

3.1 定义

使用 union 语句定义共用体。

union [union tag]

{

   member definition;

   member definition;

   ...

   member definition;

} [one or more union variables];

3.2 使用范例

#include <stdio.h>
#include <string.h>

union Data{
	int i;
	float f;
	char str[20];
}

int main(){
	union Data data;
	printf("size of data : %d \n",sizeof(data));
	data.i = 10;
	data.f = 220.5;
	strcpy( data.str, "C Programming");

        printf("data.i : %d\n", data.i);
   	printf( "data.f : %f\n", data.f);
   	printf( "data.str : %s\n", data.str);
}

输出为:

size of data : 20
data.i : 1917853763
data.f : 4122360580327794860452759994368.000000
data.str : C Programming

共用体占用的内存大小为其最大的成员大小,所有成员共享内存空间,故对一个新成员赋值之后会将旧成员覆盖。

4. 位域

4.1 位域声明与用途

在结构体中,可以定义成员变量的位宽度,可以告诉编译器只使用这几位的值作为有效值。

位域的声明如下:

struct
{
	type [member_name] : width ;
};

用途:提供了一种更好地利用内存空间的方式,例如当结构内含有多个开关变量时会节省空间。

4.2 位域使用案例:

struct {
	unsigned int a : 3;
	unsigned int b : 2;
} Age;

int main(){
	printf("size of Age: %d \n",sizeof(Age));
	Age.a = 7;
	printf("a: %d\n", Age.a );
	Age.a = 8;
	printf("a: %d\n", Age.a );
}

输出:

size of Age: 4
a: 7
a: 0

上述代码当编译 Age.a = 8,编译器会产生 warning,因为超过了标明的位使用范围。

5. C 预处理器

5.1 预处理器介绍

C 预处理器不是编译器的组成部分,但是它是编译过程中一个单独的步骤。简言之,C 预处理器只不过是一个文本替换工具而已,它们会指示编译器在实际编译之前完成所需的预处理。我们将把 C 预处理器(C Preprocessor)简写为 CPP。

所有的预处理器命令都是以井号(#)开头。它必须是第一个非空字符,为了增强可读性,预处理器指令应从第一列开始。下面列出了所有重要的预处理器指令:

指令 描述
#define 定义宏
#include 包含一个源代码文件
#undef 取消已定义的宏
#ifdef 如果宏已经定义,则返回真
#ifndef 如果宏没有定义,则返回真
#if 如果给定条件为真,则编译下面代码
#else #if 的替代方案
#elif 如果前面的 #if 给定条件不为真,当前条件为真,则编译下面代码
#endif 结束一个 #if……#else 条件编译块
#error 当遇到标准错误时,输出错误消息
#pragma 使用标准化方法,向编译器发布特殊的命令到编译器中

Tips1:只引用一次头文件

如果一个头文件被引用两次,编译器会处理两次头文件的内容,这将产生错误。为了防止这种情况,标准的做法是把文件的整个内容放在条件编译语句中,如下:

#ifndef HEADER_FILE
#define HEADER_FILE

the entire header file file

#endif

Tips2: 有条件引用:
有时需要从多个不同的头文件中选择一个引用到程序中。例如,需要指定在不同的操作系统上使用的配置参数。可以通过一系列条件来实现这点,如下:

#if SYSTEM_1
  	# include "system_1.h"
#elif SYSTEM_2
   	# include "system_2.h"
#elif SYSTEM_3
	...
#endif

但是如果头文件比较多的时候,这么做是不妥当的,预处理器使用宏来定义头文件的名称。这就是所谓的有条件引用。它不是用头文件的名称作为 #include 的直接参数,只需要使用宏名称代替即可:

#define SYSTEM_H "system_1.h"

#include SYSTEM_H

5.2 预定义宏

ANSI C 定义了许多宏。在编程中您可以使用这些宏,但是不能直接修改这些预定义的宏。

描述
DATE 当前日期,一个以 "MMM DD YYYY" 格式表示的字符常量。
TIME 当前时间,一个以 "HH:MM:SS" 格式表示的字符常量。
FILE 这会包含当前文件名,一个字符串常量。
LINE 这会包含当前行号,一个十进制常量。
STDC 当编译器以 ANSI 标准编译时,则定义为 1。

例如:

#include <stdio.h>

main(){
	printf("File : %s\n, __FILE__");
	printf("Date : %s\n, __DATE__");
	printf("Time : %s\n, __TIME__");
	printf("Line : %s\n, __LINE__");
	printf("Ansi : %s\n, __ANSI__");
}

输出如下:

File :test.c
Date :Jun 2 2012
Time :03:36:24
Line :8
ANSI :1

5.3 预处理器运算符

1. 宏延续运算符
一个宏通常写在一个单行上。但是如果宏太长,一个单行容纳不下,则使用宏延续运算符(\)。

2. 字符串常量化运算符(#)
在宏定义中,当需要把一个宏的参数转换为字符串常量时,则使用字符串常量化运算符(#)。在宏中使用的该运算符有一个特定的参数或参数列表。

例如:

#define  message_for(a, b)  \
    printf(#a " and " #b ": We love you!\n")
int main(void)
{
	message_for(Carole, Debra);
	return 0;
}

3. 标记粘贴运算符(##)
宏定义内的标记粘贴运算符(##)会合并两个参数。它允许在宏定义中两个独立的标记被合并为一个标记。

例如:

#define tokenprint(n) \
	printf("token"#n"= %d" , token##n)
int main(){
	int token34 = 34;
	tokenprint(34);
	return 0;
}

输出如下:

token34 = 34

4. defined 运算符
预处理器 defined 运算符是用在常量表达式中的,用来确定一个标识符是否已经使用 #define 定义过。如果指定的标识符已定义,则值为真(非零)。如果指定的标识符未定义,则值为假(零)。下面的实例演示了 defined() 运算符的用法:

#include <stdio.h>
#if !defined (MASSAGE)
	#define MASSAGE "you wish"
#endif
int main(void)
{
	printf("Here is the message: %s\n", MESSAGE); 
	return 0;
}

5.4 参数化的宏

CPP 一个强大的功能是可以使用参数化的宏来模拟函数。
例如:

#define square(x) x*x
#define fac(x,r) \
    r = 1; \
    while(x>0)  \
        r*=x--  \
int main(){
	int i1 = square(3);
	int i2=3,i3;
	fac(i2,i3);
	printf("i1 : %d , i3 : %d \n",i2,i3);
}

输出结果为:

i1 : 9 , i3 : 6

6. C 可变参数

6.1 可变参数介绍

有时,在编程时可能会碰到这样的情况,您希望函数带有可变数量的参数,而不是预定义数量的参数。C 语言为这种情况提供了一个解决方案,它允许您定义一个函数,能根据具体的需求接受可变数量的参数。下面的实例演示了这种函数的定义。

#include <stdio.h>
#include <stdarg.h>
double avg(int num , ...){
	va_list valist;
	double sum = 0;
	int i;
	va_start(valist,num);
	for(i = 0 ; i < num ; i++){
		sum += va_arg(va_list,int);
	}
	va_end(va_list);
	return sum / num;
}
int main(){
	printf("Average of 1,2,3 : %d\n",avg(3,1,2,3));
	return 0;
}

va_list 的实质为一个 char*,va_start,va_arg 和 va_end 为宏定义的函数:

  • va_start(ap, type) 开始获取可变参数列表中的第一个参数(...里面的第一个),也就是跳过第一个参数(即 num)。
  • va_arg(args, int) 循环获取到可变参数列表中的参数,args 指向下一个参数地址,返回的则是当前参数地址。
  • 最后一个 va_end(ap)结束标志,可能只是在程序中作为一个可变参数列表的结束标志而已(stdarg.h 里面只是仅仅定义了下,没有实现的代码部分)。

7. C 内存分配

C 语言为内存的分配和管理提供了几个函数。这些函数可以在 <stdlib.h> 头文件中找到。

序号 函数和描述
1 void *calloc(int num, int size); 在内存中动态地分配 num 个长度为 size 的连续空间,并将每一个字节都初始化为 0。所以它的结果是分配了 num*size 个字节长度的内存空间,并且每个字节的值都是 0。
2 void free(void *address); 该函数释放 address 所指向的内存块,释放的是动态分配的内存空间。
3 void *malloc(int num); 在堆区分配一块指定大小的内存空间,用来存放数据。这块内存空间在函数执行完成后不会被初始化,它们的值是未知的。
4 void *realloc(void *address, int newsize); 该函数重新分配内存,把内存扩展到 newsize
Tips:realloc 的功能:
先判断当前的指针是否有足够的连续空间,如果有,扩大 mem_address 指向的地址,并且将 mem_address 返回,如果空间不够,先按照 newsize 指定的大小分配空间,将原有数据从头到尾拷贝到新分配的内存区域,而后释放原来 mem_address 所指内存区域(注意:原来指针是自动释放,不需要使用 free),同时返回新分配的内存区域的首地址。即重新分配存储器块的地址。

本博客主要总结自:https://www.nowcoder.com/tutorial/10002


标题:C语言学习笔记
作者:YaoCheng8667
地址:https://ycisme.xyz/articles/2019/12/06/1575620879805.html