纸上得来终觉浅, 绝知此事要躬行。
主页:June-Frost
专栏:C语言
该篇将从多个部分探讨如何写出更严谨,更优雅的代码。
该文章借鉴《高质量 C++/C 编程指南》—— 林锐。
在编程中,命名是非常重要的,一个好的命名可以帮助别人更好地理解代码,提升代码的可读性和可维护性,常见的命名规则有: ①驼峰命名法(又叫小驼峰法,单词的第一个字母小写,后面的单词的首字母大写,例如:int myAge = 0
),②下划线命名法(所有字母小写,单词之间用下划线连接,例如:int first_name = 0
),③帕斯卡命名法(又叫大驼峰法,将第一个单词的首字母和其他单词的首字母均大写,例如:int FirstName = 0
),④匈牙利命名法(将变量名的前缀指定为变量的数据类型,例如:int iname = 0//这里的i代表int
)。
但是,随着不同的语言以及不同IDE的发展,其所推崇的规则是不同的,况且命名对程序的影响无足轻重,不同的项目和团队可能也有着自己的命名风格。所以我们应该关注命名的共性规则。
SL //水利行业标准
,此外单个字母也并非无用,有时可以用于循环变量。int o; int O;
,这种写法极容易混淆。int flag;
和 int newFlag;
Init();
和InitBoard();
int num1; int num2;
Windows环境下的一些建议:
MAX_LENGTH
static int s_newFlag = 0
int g_flag;//全局变量
适当的空行可以帮助分隔不同的代码块从而提高可读性,并且阅读者可以很容易地区分不同的代码段,更轻松地理解代码逻辑。在总体的布局上也会显得更加美观。
//空行
int calculateAverage(...)
{
//业务处理
}
//空行
void GetCustomerDetails(...)
{
//业务处理
}
逻辑密切相关的地方不加空行,其它地方加空行,以分隔不同的逻辑部分,有利于阅读者更好地理解代码的结构和逻辑关系。
在代码的不同部分之间增加空行,例如,在循环语句,条件语句,变量声明之间添加以助于分隔不同的代码块。
//空行
while (...)
{
description1;
//空行
if (...)
{
//业务处理
}
else
{
//业务处理
}
//空行
description2;
}
⚠但是需要注意,空行固然有着诸多好处,但是过度添加空行可能会使得代码过于稀疏,反而影响了可读性,所以一定要确保在合适的位置增加。
优雅的表达式应该是简洁、易读、清晰和易于理解的。应该使用适当的命名、合适的语法和惯用的风格来书写表达式,以便使代码更易于维护和扩展。以下是一些建议:
d = (a = b + c) + r ;
改为 a = b + c; d = a + r;
;return ((year%4 == 0)&&(year%100!=0))||(year%400 == 0);
if((*p==')'&&STTop(&s1)!='(')
||(*p==']'&&STTop(&s1)!='[')
||(*p=='}'&&STTop(&s1)!='{'))
悬空else:
#include
int main()
{
int a = 0;
int b = 2;
if (a == 1)
if (b == 2)
printf("点赞\n");
else
printf("收藏\n");
return 0;
}
注意:else 会和 最近的if 匹配 。 最好加入{} ,这样可以使得代码中的匹配更加明确。
BOOL value=0;......;if(value)//表示为真时执行
,整型类型:int value = 0;........;if(value!=0)
, 指针类型:int* value = NULL;........;if(value !=NULL)
。例如:
如果N(循环次数)大,可以采用该方式,不会打断“流水线”作业,能提高效率。
if (condition)
{
for (i=0; i<N; i++)
{
//业务处理
}
}
else
{
for (i=0; i<N; i++)
{
//业务处理
}
}
如果N比较小,两者差别不明显。以下这种更加简洁。
for (i = 0; i < N; i++)
{
if (condition)
{
//业务处理
}
else
{
//业务处理
}
}
想要将代码写的更严谨,那么必须杜绝野指针(指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)),野指针可能会导致数据错误,程序崩溃等一系列问题。
野指针是如何形成的呢?
1.指针未初始化
#include
int main()
{
int *p;//局部变量指针未初始化,默认为随机值
*p = 20;
return 0;
}
2.指针越界访问
#include
int main()
{
int arr[10] = {0};
int *p = arr;
int i = 0;
for(i=0; i<=11; i++)
{
//当指针指向的范围超出数组arr的范围时,p就是野指针
*(p++) = i;
}
return 0;
}
3.指针指向的空间释放:指针被 free 或者 delete 之后,没有置为 NULL,让人误以为是个合法的指针。
解决方案:
#include
int main()
{
int *p = NULL;
//....
int a = 10;
p = &a;
if(p != NULL)
{
*p = 20;
}
return 0;
}
char * StringCopy(char* strDestination, const char* strSource );
例如:
#include
struct S
{
int data[1000];
int num;
};
struct S s = { {1,2,3,4}, 1000 };
//结构体传参
void print1(struct S s)
{
printf("%d\n", s.num);
}
//结构体地址传参
void print2(const struct S* ps)
{
printf("%d\n", ps->num);
}
int main()
{
print1(s); //传结构体
print2(&s); //传地址
return 0;
}
如果传递一个结构体对象的时候,结构体过大,参数压栈的系统开销比较大,所以会导致性能的下降,传递指针则不会。
这一点很重要,例如可能人们会误以为 getchar 的返回类型是char,其实不然。这个函数的机制是:如果正确读取到一个字符,则返回ASCll码值,如果读取不正常,则会返回EOF(end of file,文件结束标志),EOF其实就是 -1 。对于char类型,一些编译器定义的范围为 -128 ~ 127,还有一些为 0~255 。如果是后者,在返回 -1 时就会出错,另外,对于ASCll码表的扩展字符,就算时 -128 ~ 127 也无法表示,所以采取大范围的 int 更加合适。
所以当我们自己设计函数时,要尽量避免出现误导的情况。
针对这两点,就可以模拟实现一个大致合格的 strcpy 函数。
char* StringCopy(char* strDestination, const char * strSource)
{
char* ret = strDestination;
//断言
assert(strDestination != NULL);
assert(strSource != NULL);
while (*strDestination++ = *strSource++)
{
;//空语句
}
return ret;
}
文章到这里就结束了,如果对你有帮助,你的点赞将会是我的最大动力,如果大家有什么问题或者不同的见解,欢迎大家的留言~