栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

你真的对各个关键字熟悉了吗?详细再介绍,基础再提高(小白友好)下

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

你真的对各个关键字熟悉了吗?详细再介绍,基础再提高(小白友好)下

C语言关键字再谈下
  • C语言关键字2
    • 分支语句:if
    • C语言中的 bool 类型
    • 浮点数问题
      • 浮点数总结:
    • 类型转换的关系
    • 指针变量与 "零值" 的比较
    • 选择语句 switch-case的使用
    • C程序的运行
    • 循环语句的使用
    • 对返回值的理解
    • 函数调用的内存分配
    • const关键字的认识
      • 如何取地址
      • 理解 常量指针 和 指针常量
      • const 在函数中的使用
    • 最易变的关键字:volatile
      • const 和 volatile 并不冲突
    • 最会帽子的关键字:extern
    • 结构体关键字:struct
    • 柔性数组
    • 联合关键字 union
    • 枚举关键字:enum
      • 枚举与宏定义的区别
      • 枚举与宏定义的区别
    • 缝纫师关键字:typedef 关键字
      • typedef使用的注意事项
        • typedef 与 #include

C语言关键字2 分支语句:if

分支语句:if,先执行 ( ) 中的表达式或者是函数,得到结果为真,则执行函数体,为假则不执行函数体。

在编码原则中,为了使代码执行清晰,会选择将执行概率更大的代码放在 if 判断中,而将执行概率小的放在 else 中,就是说,要将正常情况放在放在 if 后面进行处理,将异常的 / 意外的情况 放在 else中进行判断执行。

还有在使用 if-else if 结构时,最后应该以 else 结尾,最后的else 语句要 执行适当的动作,要包含合适的注释以说明为何没有执行动作,与switch 语句最后要求具有一个 default 子句是一致的。

C语言中的 bool 类型

在 C99 标准之前,也就是C89 或者说是C90 中,认为是没有 bool 类型的,而在C99 中引入了 _Bool 类型,(就是 _Bool 类型,只是在新增的头文件 stdbool.h 中,使用宏定义写成了 bool ,为了保证C和C++ 的兼容性。

在使用微软的编辑器中(VS系列或VSCode)中,大写的 BOOL 类型和 TRUE / FALSE 都是可以支持的,但是这并不是C99 的标准,所以推荐些小写 : bool 和 true / false

对于bool型的使用,要注意以下几点:

#include 
#include 
int main()
{
    int flag = 0;
	if (flag == 0)
	{
		//不推荐此写法
		//一般用于 int 型的比较,容易造成代码可读性降低
		printf("1n");
	}
	
	if (flag==false)
	{
		//不推荐次写法
		//false 仅在C99 及以上支持,代码可移植性不够
		printf("2n");
	}

	if (!flag)
	{
		//推荐,可以直观表示bool类型
		printf("3n");
	}
    
    
    return 0;
}
	

总之,bool 类型直接作为 判定 条件,不需要把操作符和特定值进行比较

浮点数问题

先来看一个简单的问题:

double x = 1.0;
double y = 0.1;
printf("%.50fn", x - 0.9);
printf("%.50fn", y);

if ((x - 0.9) == 0.1)
{
	printf("I can hear you!!");
}
else
{
	printf("Oh NO !!!");

}


从这个例子中,就要明白,在浮点数进行比较的时候,绝对不能使用 == 来进行比较!!! 浮点数本身就会有精度损失,所以会导致各种结果会有细微差别

所以,如果要比较浮点数的大小,就可通过 做差 与精度进行比较的方法来确定大小

就可以采用如下的精度比较的办法:

#include "test.h"
#include 
#include 
#include 
#define EPS 0.00000001
 int main()
{
    double x = 1.0;
    double y = 0.1;
    printf("%.50fn", x - 0.9);
    printf("%.50fn", y);

    if(fabs((x-0.9)-y) 

在浮点数的比较中,如果不使用自己宏定义的常量,也可以使用系统自定义的一个常量来作为比较对象

#include //需要包含的头文件
DBL_EPSILON		//double 最下精度值
FLE_EPSILON		//float 最小精度值

如下:

#include 
#include 

int main()
{
    double x = 0.000000001;
if (fabs(x) 

如果足够小的话,就可以得到想要的结果了:

#include 
#include 

int main()
{
    double x = 0.0000000000000001;
if (fabs(x) 

在进行比较时,尽量不要写等于号,因为即使 xxx_EPSILON 这个数很小, 也不能认为它 就是0,所以在使用它时,是不可以和 0 等价的

浮点数总结:
  1. 浮点数存储 是有精度损失的
  2. 浮点数不能进行 == 的比较
  3. 使用 if ( fabs ( a - b ) < DEL_EPSILON ) { } 来进行浮点数比较
  4. 不需要使用 <= 或者 >= 等于号是没有必要的
类型转换的关系

强制类型转换:不改变内存中的数据(二进制数是不变的),只改变对应的类型

数据转换:改变内存中的数据,比如字符串 “12345” 转换为 int 型的 12345,是由6个字节转换为了四字节,内存中的二进制数发生了变化。

指针变量与 “零值” 的比较

指针 就是地址

指针变量 就是一个变量,里面存放的内容是地址

对于空指针的判断,要注意规范

int *p = NULL;
if(p == 0)  	if(p != 0 )  		//不推荐
if ( p )  		if( !p )  			//不推荐
if( NULL == p )	if( NULL != p) 		//推荐写法
选择语句 switch-case的使用

case 作为条件判断的依据,break 则作为分支功能的存在

有以下注意点:

  • 每个case语句的结尾不要忘记了 break,否则将导致多个分支重叠(除非有意使多个分支重叠)
  • 在所有case 语句之后,一定要加上 default 语句,表示所有case都不符合的情形
  • 习惯上将 default 语句放在最后,即使将default 语句放在switch 语句的任何位置都可以,但是习惯如此,而且,在实际使用中,default语句最好是用于默认的其他情形,而不要是预想的最后一种情形。
  • 匹配一个条件执行多个语句时,要注意在一个条件中是无法定义变量的,如果一定要定义新变量,则需要使用 { } 来进行包装,使之成为一个代码块。实际编程中,并不推荐这个写法,最好将代码块封装为函数
  • 在case语句的匹配中,case后面的必须为真正的 int、char 等真正的常量,而不应该是被const 修饰的(虚假的)常量,这样的常量是无法通过编译的,但是使用 #define 定义的常量是可以通过编译的
  • 一般在正常情况下需要按照一定的顺序将case 语句进行排列,遇到特殊的情形,也应该把最频繁执行的case 语句放在前面,执行次数较少的 case 语句放在后面
  • 多个case 语句可以使用多条件匹配:
C程序的运行

任何C程序,在默认编译好之后,在运行时,都会打开三个输入输出流: 可以理解为一切皆为文件

  • stdin : 标准输入,FILE* stdin; --> 键盘
  • stdout : 标准输出,FILE* stdout; --> 显示器
  • stdrr : 错误输出,FILE* stderr; --> 显示器

所以可以将 键盘、显示器 称为 字符型设备

这样就需要正确理解 getchar 的使用,使用getcahr 进行接收字符时,先是接受各个字符,然后格式化为指定的类型,放入变量中,所以要注意该函数会默认读入输入的最后一个回车符,同样的,在进行输出时,显示在屏幕上的也全部都是字符,int 型也是以字符 格式化 并显示在屏幕上的,例子:

//在屏幕上输出的内容就是字符,并且每个都对应一个ASCII字符
int ret = printf("%dn", 1234);
printf("%dn", ret);	//四个字符和一个换行符,就是5了

break终止本层循环

continue终止本次循环

循环语句的使用
  • 在多重循环中,如果有可能,应将最长的循环放在最内层,最短的循环放在最外层,可以减少CPU 跨切循环的次数(不强求)

  • 使用for 循环控制变量的取值 采用 “半开半闭区间”写法(可以直观的表示出循环次数)

  • 循环体要尽可能的简介,循环嵌套尽量不要超过三层

  • goto 语句尽量不要使用,虽然可以灵活跳转(在代码块内,无法跨函数,跨文件)但是如果不加限制,就会破坏结构化设计风格,甚至有可能会带来错误或隐患,会跳过变量的初始化、重要的计算等语句。

对返回值的理解

先看代码:

void test()
{
	printf("hello world!");
	return 1;
}

int main()
{
	test();
    return 0;
}

这样的代码是不会报错且可以正常运行的,运行的结果就是: hello world!虽说函数是void 类型,但是却强加了一个return 语句,因为在运行的时候,是直接运行了 test() 函数,并没有明确规定接受该函数的结果的类型,如果做了明确规定,比如 int a = test(); 那么就会直接报错了。

在C语言中也要注意,void是被解释为无大小的类型,但是他本身也是有大小,如果使用VS 来 输出 sizeof (void ) 会得到结果为 0 ,而如果使用gcc(Linux) 来输出 sizeof(void ) 则会得到结果为1,要切实注意。

C语言中函数可以不使用返回值,函数的默认的返回值为 int ,但是不推荐使用这种写法,要明确函数的返回值

void 修饰函数的返回值,可以作为占位符:让用户明确知道不需要返回值,也可以告知编译器,这个无法接受返回值

void 充当函数的形参列表,告知用户/编译器,该函数不需要参数

但是 void* 可以用来接受任意指针类型,这样就可以用来设计通用接口

为什么VS中和Linux中C语言有所不同?
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mxvZOjSM-1633229320859)(D:oneDrive - iswilliam博客学习C语言关键字12.png)]

函数调用的内存分配

在 C语言中,在调用一个函数时,就是在内存的 栈区 开辟一段空间,这段空间用来存放该函数的所有内容,不会太大占用过多内存空间,也不会过小,导致无法成功放入函数,这样的可以正好放下一个函数的内存空间就可以成为 栈帧。

调用函数,形成栈帧, 函数返回,销毁栈帧(数据依然在内容中,只是指向该栈帧的指针移动了位置)

该栈帧的大小是由 程序在编译时,对各个变量类型的大小的预估得到的,所以大小是正好合适的。

这样也可以解释:为什么 临时变量是具有临时性的?

因为栈帧结构在函数调用完毕后,需要被释放

下面个例子可以更好的理解临时性:

#include 
#include 

int GetData()
{
	int x = 0x11223344;
	printf("run get data!n");
	return x;
}
int main()
{
	int y = GetData();
	printf("ret: %xn", y);



	return 0;
}

只看结果的话,在函数调用时,成功的输出了函数中的临时变量的内容,而不是取到了 调用函数中的变量

通过反汇编的方式来观察地址跳转,可以发现: 函数的返回值,通过寄存器的方式返回给函数调用方,而且无论是否有变量接受函数返回值,函数都会将返回值放入寄存器中

const关键字的认识

const 修饰的变量不可以被直接修改,但是可以通过修改指针来实现修改:

	const int a = 10;	//a 不可以被直接修改
	int* p = (int*)&a;	//转换为 指针类型
	printf("before: %dn", a);

	*p = 20;	//解引用

	printf("after: %dn", a);

所以使用const 进行修饰的意义是:

  • 告知编译器进行 直接修改式 检查,防止被修改,可以防止可能出现的逻辑问题
  • 告知其他程序员,此变量后续 不要进行修改,有"自描述"含义

真正的不可修改:

所以说使用const 修饰的变量并不真的是变成了常量,仅仅是作为不可轻易修改的变量:

此段代码在 VS 编译器下无法正常运行,在 gcc (GNU 拓展) 下就可以正常运行了,也就是在Linux 平台可以正常编译运行,所以处于跨平台性,不要出现这种写法

如何取地址

在C语言中,任何变量在取地址时,都是从最低地址开始的

理解 常量指针 和 指针常量

常量指针:

int a = 10;
int b = 20;
//常量指针
const int* p = &a;	//p指向的变量不可以直接被修改,保存的地址可以被修改
//int const* p = &a;	//表达含义与上相同,一般使用上面的定义方法,比较规范
/

	return 0;
}

联合体内所有变量的其实地址都是一样的(都是从低地址开始存放),每个联合体内的变量都可以认为是第一个元素

而且在数据存储时,采用的是小端存储,就是数据的低权值位是存储在联合体的低地址位 的。

还有一点,在判断联合体占用空间大小时要考虑内存对齐!

#include
union un
{
	int a;
	char b[5];
    //在考虑内存对齐整除时,将这个数组看为char,为1,但是在计算大小时看做整体,为5
};

int main()
{
	printf("un 的大小为:%dn", sizeof(union un));

	return 0;
}

联合体内存对齐:联合体所占的内存大小,需要可以整除联合体内任何一个变量的大小

枚举关键字:enum

枚举关键字定义的均为常量,枚举变量可以直接作为常量使用,甚至可以将枚举常量看做是整数

如果对枚举常量进行赋值,则其后是连续赋值的,也可以实现阶段性赋值

#include

enum color
{
	RED=1,
	YELLOW,
	BLACK=10,
	GREEN,
	BLUE=100
};

int main()
{
	enum color c = RED;
	printf("%dn", RED);
	printf("%dn", YELLOW);
	printf("%dn", BLACK);
	printf("%dn", GREEN);
	printf("%dn", BLUE);


	return 0;
}

使用枚举可以很好的描述事物需要的特性(状态),尤其是含有多个,不适合使用 define

而且使用枚举可以很好的避免"魔鬼数字"问题,因为使用枚举可以很清晰的表名这个一个枚举变量,而不是像数字一样,如果没有注释说明,他人阅读代码很难明白这个数字的含义,可以增加 自描 属性

枚举与宏定义的区别

如果在项目中常量使用不多且相关性较低,就使用宏定义,否则就可以选择使用枚举

  • #define 宏常量是在编译阶段进行简单替换,枚举常量则是在编译的时候确定其值
  • 一般在调试器中,可以调试枚举常量,却不能调试宏常量

如果对枚举常量进行赋值,则其后是连续赋值的,也可以实现阶段性赋值

#include

enum color
{
	RED=1,
	YELLOW,
	BLACK=10,
	GREEN,
	BLUE=100
};

int main()
{
	enum color c = RED;
	printf("%dn", RED);
	printf("%dn", YELLOW);
	printf("%dn", BLACK);
	printf("%dn", GREEN);
	printf("%dn", BLUE);


	return 0;
}

使用枚举可以很好的描述事物需要的特性(状态),尤其是含有多个,不适合使用 define

而且使用枚举可以很好的避免"魔鬼数字"问题,因为使用枚举可以很清晰的表名这个一个枚举变量,而不是像数字一样,如果没有注释说明,他人阅读代码很难明白这个数字的含义,可以增加 自描 属性

枚举与宏定义的区别

如果在项目中常量使用不多且相关性较低,就使用宏定义,否则就可以选择使用枚举

  • #define 宏常量是在编译阶段进行简单替换,枚举常量则是在编译的时候确定其值
  • 一般在调试器中,可以调试枚举常量,却不能调试宏常量
  • 枚举可以一次定义大量相关的常量,而#define宏 一次只能定义一个
缝纫师关键字:typedef 关键字

typedef 的作用就是给变量做重命名

使用方法:

#include 

using namespace std;

//将关键字名 重命名
typedef unsigned int u_int;

//将结构体 进行重命名
typedef struct stu
{
	char name[16];
	int age;
	char sex;
}stu_t;

int main()
{
	u_int x = 0;
	cout << "关键字重命名:" << x << "n";

	stu_t s;
	s.age = 22;
	cout << "结构体重命名: " << s.age << "n";


	return 0;
}

在实际项目中,要切忌对 变量类型进行过度重命名,这样会大大降低 代码的可读性,这里推荐对结构体进行重命名,而不要对其他类型再进行重命名了

typedef使用的注意事项

在 C/C++ 中要注意,在实际编码过程中,要注意编码规范
在这里,a 为指针(可以理解为靠 * 更近),b 为整形

//甚至可以这样定义,但是不推荐这样使用
int *a = NULL, b=0;	//a 为指针,b 为整形

但是,如果使用 typedef 进行重命名之后,在使用这个语句进行定义后,就会发现 a,b 均为指针了

所以在理解typedef 时,不能简单的将其理解为给该类型取了个别名,而是可以认为 姓名字是 一个新的 类型,这个类型可以对 使用该类型进行定义的变量 进行统一定义

所以在实际开发中,如果要定义指针或是整形,最好单独的 分开 进行定义

typedef 与 #include

typedef 是类型重命名,但本质上并不属于宏 的替换,相当于是一个新的类型,所以在使用这个类型时,和使用 double/float 等没有本质区别,而 如果是 #define ,相当于全局替换,将所有的 该变量 都替换为 宏定义的变量

在代码中也可以看到,使用 #define 的效果和 使用 int* 效果是一模一样的

在使用 typedef 给变量起别名之后,这个变量就不再是之前的那个变量了,而可以看做是和原关键字效果一样的另一个关键字,并且能力受限

typedef int int32
int main()
{
    //错误写法,会直接报错,在使用typedef进行取别名之后,就无法再继续满足之前该关键字所有的功能了
    unsigned int32 a;
    
    return 0;
}

关于C语言关键字的总结就基本完成了,后面会继续更新其他内容的!(。・∀・)ノ*
感谢观赏,慢慢提高

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/290148.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号