程序设计基础(C&C++)| 第一章:绪论

程序设计基础(C&C++)| 第一章:绪论最后修改时间 2024 07 14

大家好,欢迎来到IT知识分享网。

最后修改时间:2024/07/14



推荐书籍:

  1. C与C++程序设计
    在这里插入图片描述
  2. C程序设计语言
    在这里插入图片描述
  3. 明解C语言
    在这里插入图片描述
  4. C程序设计
    在这里插入图片描述
  5. Accelerated C++
    在这里插入图片描述
  6. C++ Primer
    在这里插入图片描述
  7. Essential C++
    在这里插入图片描述



软件与程序

计算机硬件与软件

硬件是什么?以我们平常使用的台式机为例:

在这里插入图片描述

  1. 显示器
  2. 主板
  3. CPU
  4. 内存条
  5. 显卡
  6. 电源
  7. 光驱
  8. 硬盘
  9. 键盘
  10. 鼠标
    如果只有计算机是没什么意义的,必须安装操作系统和各种应用程序。
需求分析 -> 设计 -> 编码 -> 测试 -> 维护 What to do How to do Code Test Maintainence 

五个阶段,并生成各种文档资料。

程序设计语言

计算机不能直接执行高级语言的程序(源程序),通常有解释方式和编译方式两种方法在计算机上执行程序(目标程序):

  • 解释
    在这里插入图片描述

解释类的执行类似于日常生活中的同声翻译应用程序的源代码:一边由相应的语言解释器翻译成目标代码,一边执行。因此,效率相对较低,而且无法生成可独立执行的可执行文件。应用程序无法脱离这个解释器。但,这种方式也相对灵活,可以动态调整、修改。

  • 编译
    在这里插入图片描述

C语言是高级语言之一。C语言适合做什么?

  1. 编写操作系统和基础工具
  2. 对运行效率要求较高的系统:设备驱动程序,高性能、实时中间件,嵌入式领域,并发程序设计等
  3. 继承和维护已有的C代码

软件开发环境与编程

开发环境部署

本人使用 clion 进行开发,创建c语言项目可以参考这个文档 1

编程实践

1. hello world

认识C语言从运行这个程序开始:

#include <stdio.h> // #表示预处理,后面的include表示本项目要包含 stdio.h 这个文件。stdio.h 是一个和输入、输出操作相关的头文件,凡是需要进行输入和输出操作的就需要通过预处理操作包含该文件。 int main{ 
         // main函数即主函数,每一个项目有且只能有一个main函数。如果运行时出现重复定义的提示,则一定是有不止一个main函数出现了。 printf("Hello World!\n"); return 0; // return 0表示程序正常执行完毕,没有错误。 } 

2. 求整数的绝对值

#include <stdio.h> /*涉及到输入、输出操作,需要预处理include stdio.h文件*/ int main() { 
          int num; /*定义一个名为num的变量,变量的数据类型为int*/ int abs; /*代表绝对值运算结果:绝对值*/ int r; printf("请输入一个整数:"); r=scanf("%d", &num); /*scanf_s来获取要输入的数据,%d表示要输入的数据是整数类型,这个整数存在num这个变量中。&表示存在num这个变量所在的地址空间中*/ printf("返回值:%d\n", r); if (r==1) { 
          if (num>0) abs=num; else abs=-num; printf("%d的绝对值:%d\n", num, abs); } else printf("输入错误!\n"); return 0; } 

3. 求长方体的体积

#include <stdio.h> /*涉及到输入、输出操作,需要预处理include stdio.h文件*/ #include <stdlib.h> int volume(int x, int y, int z) /*定义volume函数*/ { 
           return (x * y * z); /*将volume值返回调用处*/ } int main() { 
           int x, y, z, v; /*定义同一类型的变量可以写在同一行*/ int r; printf("请输出立方体3边长度的整数: "); r = scanf("%d %d %d", &x, &y, &z); printf("返回值:%d\n", r); if (r==3 && x>0 && y>0 && z>0) { 
           v = volume(x, y, z); printf("体积是:%d", v); } else printf("error\n"); return 0; } 

算法与流程图

程序 = 算法 + 数据结构 + 程序设计方法 + 语言工具和环境

算法

举例
BEGIN READ numb IF numb > 0 abs = numb ELSE abs = -numb PRINT abs END 
#include <stdio.h> /*涉及到输入、输出操作,需要预处理include stdio.h文件*/ void main() { int numb; /*代表一个整数*/ int abs; /*代表运算结果:绝对值*/ scanf("%d", &numb); /*输入整数的值*/ if (numb > 0) abs = numb; else abs = -numb; printf("numb的绝对值是:%d\n", abs); } 

流程图

在这里插入图片描述
① 起止框:输入输出框,表示程序的开始和结束。
② 处理框:表示一种处理功能或者程序段框里面用文字简单的描述功能。
③ 选择框:判断框,在这里进行判断来决定走左边那条路还是右边那条路,通常用文字来标注这个条件为真则走这条,为假则走这条。
④ 连接框:这个框里面会有字母。当我们的流程图要跨页表示时,或者有可能出现流向线交叉时,就用连接框来表示彼此之间的关系。
⑤ 流向线:通过这种有方向的线把各种逻辑框串联起来,表达算法的步骤序列。




编程规范

1. 命名

  • 标识符
    • 标识符的命名要清晰、明了,有明确含义,符合阅读习惯,容易理解
    • 标识符的命名使用驼峰风格
      驼峰风格(CamelCase)大小写字母混用、单词连在一起,不同单词间通过单词首字母大写来分开。按连接后的首字母是否大写,又分大驼峰(UpperCamelCase)、小驼峰(LowerCamelCase)。
    • 作用域越大, 命名应越精确
  • 函数
    • 函数的命名遵循阅读习惯
      在这里插入图片描述
  • 变量
    • 全局变量应增加g_前缀,函数内静态变量命名不需要加特殊前缀
    • 局部变量应该简短,且能够表达相关含义
    • 避免滥用 typedef / #define 对基本类型起别名
    • 避免函数式宏中的临时变量命名污染外部作用域

2. 排版风格

3. 注释风格

4. 文件

头文件职责:

  • 头文件是模块或文件的对外接口
  • 头文件中适合放置接口的声明,不适合放置实现(内联函数除外)
  • 头文件应当职责单一。头文件过于复杂,依赖过于复杂是导致编译时间过长的主要原因
  • 每一个 *.c 文件都应该有相应的 .h 文件,用于声明需要对外公开的接口
  • 每一个功能模块都应该提供一个单独的 .h 文件,用于声明模块整体对外提供的接口
  • 头文件的扩展名只使用 .h,不使用非习惯用法的扩展名,如 .inc
  • 禁止头文件循环依赖
    编写头文件时应当防止 循环依赖,指a.h包含 b.h、b.h包含c.h,c.h包含a.h。任何一个头文件修改,都导致所有包含了a.h / b.h / c.h 的代码全部重新编译一遍。而是应该单向依赖,比如a.h包含b.h,b.h包含c.h,而c.h不包含任何头文件,则修改a.h不会导致包含了b.h / c.h 的源代码重新编译。
    在这里插入图片描述

  • 禁止包含用不到的头文件
  • 头文件应当自包含
    自包含就是任意一个头文件均可独立编译。如果一个文件包含某个头文件,还要包含另外一个头文件才能编译通过的话,给这个头文件的用户增添不必要的负担。
    比如,如果a.h不是自包含的,需要包含b.h才能编译,则:
    每个使用a.h头文件的.c文件,为了让引入的a.h内容编译通过,都需要额外包含b.h头文件
    额外的头文件b.h必须在a.h之前进行包含,这在包含顺序上产生了依赖



  • 头文件必须编写 #define 保护,防止重复包含
  • 禁止通过声明的方式引用外部函数接口、变量
    只能通过包含头文件的方式使用其他模块或文件提供的接口
    通过extern声明的方式使用外部函数接口、变量,容易在外部接口改变时可能导致声明和定义不一致。同时这种隐式依赖,容易导致架构腐化
    在这里插入图片描述– 禁止在 extern “C” 中包含头文件
    在extern “C”中包含头文件,有可能会导致extern “C”嵌套,部分编译器会对 extern “C” 嵌套层次有限制,嵌套层次太多会编译错误。extern “C” 通常出现在C,C++混合编程的情况
    在extern “C”中包含头文件,可能会导致被包含头文件的原有意图遭到破坏,比如链接规范被不正确地更改
    在这里插入图片描述





5. 函数

6. 宏

宏的缺点

  • 宏缺乏类型检查,不如函数调用检查严格
  • 宏展开可能会产生意想不到的副作用,如 #define SQUARE(a) *(a)这样的定义
  • 以宏形势写的代码难以调试、难以打断点,不利于定位问题
  • 宏相当于代码展开,只是减少代码编辑空间时间,但不会减少编译后的代码空间的


宏的场景

  • 用到了宏的特殊功能,如 ‘#’、‘’,不定长参数
  • 用到了预定义宏值,如 ‘FILE’,‘TIME’ 等
  • 包含了不完整语句的封装,比如for循环的循环条件封装
  • 经测试,不使用函数式宏,性能无法满足需求的

以下是宏的使用时没有严格使用括号而导致副作用产生的例子:

在这里插入图片描述
当使用多条语句的函数宏时,宏本身没有代码块的概念,当宏在调用点展开后宏内定义的表达式和变量融合到调用代码中,可能会出现变量名冲突和宏内语句被分割等问题。通过 do-while(0)显式为宏加上边界,让宏有独立的作用域,并且跟分号能更好的结合而形成单条语句,从而规避此类问题。
错误示例如下:

#define FOO(x)\ voidprintf("arg is %d\n", (x));\ DoSomething((x)); 

正确示例如下:

#define FOO(x) do{ 
               \ (void)printf("arg is %d\n", (x));\ DoSomething((x));\ }while (0) 

宏使用场景示例:

for (i=1; i<10; i++) FOO(i); 
#define SQUARE(a)((a)*(a)) int a=5; int b; b=SQUARE(a++); // 实际a自增加了2次,SQUARE(a++)展开后为((a++)*(a++)),变量a自增加了2次,其值为7,而不是预期的6 

正确示例如下:

#define SQUARE(a)((a)*(a)) int a=5; int b; b=SQUARE(a); a++; // 结果a=6,只自增了一次 

7. 变量

7.1

变量在使用时,应始终遵循职责单一原则,尽量不用或少用全局变量:

  • 便于传递参数;减少传递参数的时间,减少程序的运行时间
  • 数据共享

缺点

  • 破坏函数的独立性和可移植性,使函数对全局变量产生依赖,存在耦合
  • 降低函数的代码可读性和可维护性。当多个函数读写全局变量时,某一时刻取值可能不是确定的,对于代码的阅读和维护不利
    在并发编程环境中,使用全局变量会破坏函数的可重入性,需要增加额外的同步处理才能确保数据安全

如果要在其他模块使用全局变量,可以将该全局变量封装为函数并提供函数接口来对外和其他函数做数据交互,函数接口如下所示:

unsigned int GetRecvBufSize() { 
                return g_recvBufSize(); } 

则,原先调用全局变量方式 extern unsigned int g_recvBufSize; 可以改为现在的函数接口来调用目标变量 extern unsigned int GetRecvBufSize();

7.2
void Foo(...) { 
                int data; if (...){ 
                data = 100; } Bar(data); // 这里传递的data是未初始化过的 } 
7.3
const double pi=3. 

使用魔鬼数字的示例如下:

totalMons = year*12 + mon; if (chipType == 12){ 
                // 12在这里没有任何的说明,即魔鬼数字 ... } 

8. 实践

  1. 表达式的比较,应当遵循左侧倾向于变化、右侧倾向于不变的原则。
    常数放右边,更符合人的阅读习惯,如下所示:
if (score == 60){ 
               PASS;} if (60 == score){ 
               PASS;} if (60 <= score){ 
               PASS;} 
  1. 含有变量自增或自减运算的表达式中禁止再次引用该变量。
    含有变量自增或自减运算的表达式中如果再引用该变量,其结果在C标准中未明确定义。各个编译器或者同一个编译器不同版本实现可能会不一致。

错误示例如下:

x = b[i] + i++; // 运算中b[i]和i++的先后顺序并不明确 

正确示例如下:

x = b[i] + i; i++; // 含有变量自增/自减运算的表达式应单独一行 
  1. 用括号明确表达式的操作顺序,避免过分依赖默认优先级。
  2. 赋值语句不要用作函数参数,不要用在产生布尔值的表达式里。
    赋值语句作为函数参数来使用,因为比如说一些函数存在减值或者提前结束等执行不完整的特殊情况,那么可能你以为它执行了然而实际上它并没有执行这个赋值语句,则结果可能非预期,而且可读性差。

错误示例如下:

int Foo(...) { 
                int a=0, int b; if ((a==0) || ((b=Fun1()) > 10){ 
                (void)printf("a:%d\n", a); } (void)printf("b:%d\n", b); } 

在if语句中,会根据条件依次判断,如果前一个条件已经可以判定整个条件,则后续条件语句不会再运行,所以可能导致期望的部分赋值没有得到运行。

正确示例如下:

x = y; if (x!=0){ 
                Foo(); } 

错误观点:

  • 禁止使用goto
  • 滥用goto。比如所有函数都用goto实现统一出口
  • 用do-while(0) + break来模拟 goto 跳转
signed char ch; unsigned short int exam; ch = -1; exam = ch; // 这时编译器不产生告警,此时exam为0xFFFF 

参考


  1. CLion创建C语言项目实现多个.c文件分别运行 ↩︎












免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/125819.html

(0)
上一篇 2025-09-23 21:10
下一篇 2025-09-23 21:20

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信