C++:不止于C

        C++(发音为“see plus plus”)由贝尔实验室的 Bjarne Stroustrup 开发,作为 C 的扩展,始于 1979 年。C++ 为 C 语言添加了许多新功能,也许最好将其视为 C 的超集,尽管这并不严格正确(因为 C99 引入了一些在 C++ 中不存在的功能)。C++ 声名鹊起主要是因为它是一种面向对象的语言。

一、编程语言与编程范式

(一)编程范式

        编程范型、编程范式或程序设计法(英语:Programming paradigm),(范即模范、典范之意,范式即模式、方法),是一类典型的编程风格,是指从事软件工程的一类典型的风格(可以对照方法学)

        编程范型提供了(同时决定了)程序员对程序执行的看法。例如,在面向对象编程中,程序员认为程序是一系列相互作用的对象,而在函数式编程中一个程序会被看作是一个无状态的函数计算的序列。

        正如软件工程中不同的群体会提倡不同的“方法学”一样,不同的编程语言也会提倡不同的“编程范型”。一些语言是专门为某个特定的范型设计的(如Smalltalk和Java支持面向对象编程,而Haskell和Scheme则支持函数式编程),同时还有另一些语言支持多种范型(如Ruby、Common Lisp、Python和Oz)。

        很多编程范型已经被熟知他们禁止使用哪些技术,同时允许使用哪些。例如,纯粹的函数式编程不允许有副作用;结构化编程不允许使用goto。可能是因为这个原因,新的范型常常被那些习惯于较早的风格的人认为是教条主义或过分严格。然而,这样避免某些技术反而更加证明了关于程序正确性——或仅仅是理解它的行为——的法则,而不用限制程序语言的一般性。

        编程范型和编程语言之间的关系可能十分复杂,由于一个编程语言可以支持多种范型。例如,C++设计时,支持过程化编程、面向对象编程以及泛型编程。然而,设计师和程序员们要考虑如何使用这些范型元素来构建一个程序。一个人可以用C++写出一个完全过程化的程序,另一个人也可以用C++写出一个纯粹的面向对象程序,甚至还有人可以写出杂揉了两种范型的程序。

多范式语言

        许多现代编程语言支持多种编程范式,这使得程序员可以根据需要选择最适合的编程风格。这种灵活性提高了语言的适应性和表现力。

  • 命令式编程范式(Imperative Programming):按照一系列指令的顺序执行程序,通过修改变量的状态来实现计算。常见的命令式编程语言包括C、Java和Python。
  • 面向对象编程范式(Object-Oriented Programming,OOP):将程序组织为对象的集合,每个对象都有自己的状态和行为。通过定义类和实例化对象来实现代码的组织和重用。常见的面向对象编程语言包括Java、C++和Python。
  • 函数式编程范式(Functional Programming):将程序视为一系列函数的组合,避免使用可变状态和共享数据。函数是不可变的,且不应该有副作用。常见的函数式编程语言包括Haskell、Lisp和Scala。
  • 声明式编程范式(Declarative Programming):关注描述问题的逻辑,而不是指定如何解决问题的步骤。常见的声明式编程范式包括逻辑编程(如Prolog)和数据库查询语言(如SQL)。
  • 并发编程范式(Concurrent Programming):处理多个任务同时执行的代码。并发编程范式包括多线程编程、并行编程和事件驱动编程。
  • 泛型编程范式(Generic Programming):编写可重用的代码,不依赖于具体的数据类型。通过参数化类型和泛型函数来实现。C++中的模板是一种泛型编程的实现方式。
  • 面向切面编程(Aspect-Oriented Programming,AOP):将横切关注点(如日志、事务管理)从主要逻辑中分离出来,以提供更好的模块化和可维护性。典型代表框架:AspectJ。
  • 响应式编程(Reactive Programming):通过使用流(Stream)和异步事件来处理数据流和事件流,使程序能够以响应式、弹性和容错的方式进行处理。典型代表框架:RxJava、Reactor。

(二)C++中的编程范式

1. 过程式编程范式

        在 C++ 中,过程式编程范式以清晰的步骤和有序的指令为特点。例如,使用一系列函数来完成特定任务,每个函数专注于一个具体的操作。通过依次调用这些函数,按照预定的顺序执行指令,实现程序的功能。这种范式在处理简单、直接的任务时非常有效,代码逻辑直观易懂。

2. 面向对象编程范式

        C++ 中的面向对象编程范式将数据和对数据的操作封装在对象中。对象具有属性和方法,通过对象之间的消息传递和交互来完成程序的功能。例如,创建一个汽车类,包含速度、颜色等属性和加速、刹车等方法。不同的汽车对象可以独立地操作自己的状态,提高了代码的模块化和可重用性。

3. 泛型编程范式

        C++ 的泛型编程范式借助模板机制,使代码能够适应不同的数据类型。例如,定义一个通用的排序函数模板,可以对整数、浮点数甚至自定义类型的数组进行排序。这样大大减少了代码的重复,提高了代码的通用性和灵活性。

二、编写一个C++程序

        在多数情况下,一个合格的C语言程序,是一个合法的C++程序(反之不然),所以,在已有的C语言基础上,可以很快过渡到C++。以下将进行这种快速过渡。

(一)什么是C++程序

        读者可能对以下的叙述很熟悉,因为它也是C的叙述。

        A C++ program is a sequence of text files (typically header and source files) that contain declarations. They undergo translation to become an executable program, which is executed when the C++ implementation calls its main function.

        C++程序是包含声明的一系列文本文件(通常是头文件和源文件)。它们经过转换成为可执行程序,当C++实现调用其main函数时执行。

 

(二)注释、关键字、标识符

        Certain words in a C++ program have special meaning, and these are known as keywords. Others can be used as identifiers. Comments are ignored during translation.

        C++程序中的某些单词具有特殊含义,这些单词被称为关键字。其他可以用作标识符。注释在翻译过程中被忽略。

1、注释

        C++的注释和C没有区别,只有两种,单行注释(Single-line comments)和多行注释(Multi-line comments),值得说明的是:

  • At the library, program, or function level, use comments to describe what
    在库、程序或函数级别,使用注释来描述什么.
  • Inside the library, program, or function, use comments to describe how
    在库、程序或函数内部,使用注释来描述如何.
  • At the statement level, use comments to describe why
    在语句级别,使用注释来描述原因.
  • 不要在单行注释最后添加\
  • 不要轻易嵌套注释

2、关键字

        关键字,也被称为保留字,以下为C++的关键字一览表,应该说在C语言中常用的关键字都在里面,false和true变成了关键字,这意味着不再需要手动添加stdbool库了。

图源: https://www.learncpp.com/

3、标识符

        标识符,通常用来引入实体,由任意长(不建议太长)的数字序列、下划线、小写和大写拉丁字母以及大多数Unicode字符组成的非保留字字符序列,区别大小写,不能数字开头。

        关于标识符,其实比较重要的是它的命名规范

  • 在 C++ 中,变量名称应以小写字母开头。如果变量名称是单个单词或首字母缩略词,则整个内容应用小写字母编写
  • 大多数情况下,函数名称也以小写字母开头(尽管在这一点上存在一些分歧),但是main函数就是如此。
  • 以大写字母开头的标识符名称通常用于用户定义的类型(类、枚举、结构)
  • 如果变量或函数名称是多词,则有两种常见的约定:用下划线分隔(有时称为 snake_case)或首字母大写(有时称为 camelCase,因为大写字母像骆驼上的驼峰一样伸出来)。

(三)对象与变量

         An object is a region of storage (usually memory) that can store a value, and has other associated properties。

        对象是一块可以存储值得内存区域,有着其它相关联的属性。       

         在一般编程中,术语“对象”通常是指内存中未命名的对象、变量或函数。在 C++ 中,术语对象的定义较窄,不包括函数。         

         Although objects in C++ can be unnamed (anonymous), more often we name our objects using an identifier. An object with a name is called a variable.

        虽然C++中的对象可以未命名(匿名),但我们更经常使用标识符来命名对象。有名字的对象称为变量。

        变量的定义,学过C的读者并不会陌生,但是定义变量,我们需要涉及到另一个概念——数据类型(data type) 。

        A data type (more commonly just called a type) determines what kind of value (e.g. a number, a letter, text, etc…) the object will store.

        数据类型(通常简称为类型)决定了对象将存储哪种值(例如,数字、字母、文本等)。

        不过,并不要担心这些,在这阶段,读者只需要知道,在C语言中的所有数据类型,都可以在C++中顺利使用。

        当程序运行时(称为运行时runtime)时,变量将被实例化。实例化(Instantiation)意味着对象将被创建并分配一个内存地址,实例化的对象有时称为实例(instance)。

        可以同时定义多个变量,不过这并不建议,单独的变量定义,这会很清晰帮助我们知道,我们所定义的变量有没有被初始化(initialization),没有初始化(uninitialized)通常有着潜在的隐患。

 1、初始化

        C++中的初始化,比C更加多样化,这可能会有点花里胡哨,不过这并不是初学者的论调,读者可以在后面的学习中自行分析,但不管怎么样,接下来,我将介绍它们:

        用于初始化的语法,称之为初始化器(initializer),有六种:

int a;         // no initializer (default initialization)
int b = 5;     // initial value after equals sign (copy initialization)
int c( 6 );    // initial value in parenthesis (direct initialization)

// List initialization methods (C++11) (preferred)
int d { 7 };   // initial value in braces (direct list initialization)
int e = { 8 }; // initial value in braces after equals sign (copy list initialization)
int f {};      // initializer is empty braces (value initialization)

默认初始化(Default initialization),在C语言中就是不初始化;

拷贝初始化(Copy initialization),因为我们使用了赋值运算符,通常情况下,这将把右边的值拷贝给左边的对象。这种初始化对于复杂的数据类型或者大一点的对象,效率不高,拷贝初始化是对C语言的继承而已。

直接初始化Direct initialization ),这和拷贝初始化类似,不过省略了赋值运算符,这最初被设计用于解决拷贝初始化对复杂类型拷贝效率不高的问题,但是由于和函数调用符很像,在某些时候让人有点难以辨别。

下面三种感觉就像是对上面的替换,不过不仅仅是如此,它们是更现代化的初始化器,读者可以在后面的学习中体会到它们的益处。

2、列表初始化

缩小转换

        通常,在C语言中,当不同的数据类型进行拷贝赋值时,会发生隐式转换,但在列表初始化中,这被禁止

        缩小转换指的是那些可能会丢失信息或导致精度降低的类型转换。

  • 禁止从浮点类型到整数类型的转换。
  • 禁止从浮点类型 T 转换为另一种浮点转换秩既不大于也不等于 T 的浮点类型,除非转换结果是常量表达式且满足以下条件之一:转换后的值是有限的且转换不会溢出;转换前后的值不是有限的。
  • 禁止从整数类型到浮点类型的转换,除非源是常量表达式且其值能完全存储在目标类型中。 禁止从整数或无范围枚举类型转换为不能表示原始所有值的整数类型,除非源是宽度 w 小于其类型(对于枚举类型则是其底层类型)的位字段,且目标类型能表示宽度为 w 且符号与原始类型相同的假设扩展整数类型的所有值;或者源是常量表达式且其值能准确存储在目标类型中。
  • 禁止从指针类型或指针到成员类型到 bool 的转换。 
值初始化

        值初始化(Value initialization )是指使用空花括号来初始化变量。对于大多数内置类型来说,值初始化通常意味着将变量初始化为零或空值,发生归零的情况,被称之为零初始化(zero initialization)。        

(四)字面量与操作符

 1、字面量 (Literals)

       字面量,也称之为字面常量(literal constant)是直接插入到源码中的固定值,它们也是对象,不过没有名字,和变量不同的是,它们的值不可更改,这就是为什么称之为常量。

        在C语言的许多字面量语法,读者都可以在C++中尝试,它们几乎是相通的。每一个

2、运算符 (Operators)

        运算符是用于执行特定类型的操作的符号。C语言中的运算符同样适用于C++。对于每一个运算符都有若干个操作数,并产生一个返回值,对于类型不同的操作数,会进行隐式转换以进行后续的操作,操作总是不可可避免地产生副作用。

(五)语句和表达式

       An expression is a sequence of operators and their operands, that specifies a computation. Statements are fragments of the C++ program that are executed in sequence.The body of any function is a sequence of statements.

        表达式是指定计算的运算符及其操作数序列。语句是按顺序执行的C++程序的片段。任何函数的主体都是一系列语句。

        C++的语句有着和C的语句一样的类型但不同 

  • Declaration statements 声明语句
  • Jump statements 跳转语句
  • Expression statements 表达式语句
  • Compound statements 复合语句
  • Selection statements (conditionals) 选择语句(条件)
  • Iteration statements (loops) 迭代语句(循环)
  • Try blocks Try语句块

 

(六)函数与头文件

        函数是一系列语句的集合,规范来说,函数是C++实体,它们将语句序列(函数体)与名称和零个或多个函数参数的列表相关联。

​        在C语言中的函数语法在C++中是适用的,这点不再赘述,同样的对于main函数,在C++中也是一样的地位,略微不同,这里值得说明的是头文件,正如下面的说明

        Notes: xxx.h headers are deprecated in C++98 and undeprecated in C++23. These headers are discouraged for pure C++ code, but not subject to future removal.

        注意:xxx.h标头在C++98中废弃,在C++23中不推荐使用。纯C++代码不鼓励使用这些标头,但将来不会删除。

        形如xxx.h的头文件,是在C语言中的头文件包含形式,但是在C++中,普遍不使用类似的头文件包含,以标准输入输出库为例:

 C\Rightarrow \# include<stdio.h> \\ C++ \Rightarrow \#include<iostream>

        C语言使用stdio.h,而C++使用iostream库,但是如果要使用C语言的诸如printf函数,我们还是得包含这个头文件,所以C++为我们保留了对应的库,不过值得注意的对于有些库,在C++中不一定恰好是原来的库名。

        For some of the C standard library headers of the form xxx.h, the C++ standard library both includes an identically-named header and another header of the form cxxx .The intended use of headers of form xxx.h is for interoperability only. It is possible that C++ source files need to include one of these headers in order to be valid ISO C. Source files that are not intended to also be valid ISO C should not use any of the C headers.
        对于一些形式为xxx.h的C标准库标头,C++标准库都包括一个同名标头和另一个形式为cxxx的标头。形式为xxx.h的标头的预期用途仅用于互操作性。C++源文件可能需要包含这些标头之一才能成为有效的ISO C。不打算成为有效的ISO C的源文件不应使用任何C标头。

(七)基本IO

        前面,我们大致过了一遍C++的基本语法,现在,正式写一个C++的简单程序,这个程序对于初学者可能并不轻松,内容如下:

#include <iostream> // 包含输入输出流头文件

int main() {
    std::cout << "Hello, World! Please enter your name: "; // 输出提示信息
    std::string name; // 定义一个字符串变量
    std::cin >> name; // 读取用户输入
    std::cout << "Hello, " << name << "! Welcome to the world of C++." << std::endl; // 输出欢迎信息

    return 0; // 主函数正常结束返回0
}

        首先,我们需要一个标准输入输出流的库iostream,它包含我们需要使用到的一些对象。 

1. 输出:std::cout

std::cout是一个预定义的对象,代表标准输出流。我们通常使用它来向控制台打印信息。可以将其分开为三个部分:

std::cout \Rightarrow std \ + \ :: \ + \ cout

        std是一个名为std命名空间,我们曾在C中讨论过命名空间,不过命名空间在C语言中并不强势,或者说明显,甚至说好用。C++中,这点被显著加强了,后面的::,叫做作用域解析运算符,而cout,意思是character\ out,字符输出,更准确说应该是字符流输出。更形象地,我们认为std为姓氏,而cout是名,std::cout意在寻找一个std家族中的cout成员。std::cout控制输出到一个与stdout相关联的流缓冲区。但是仅仅是这样是不够的,因为我们缺少“输出内容”。

<< content

的作用就是将content定向到输出流缓冲区,<<这时候不再是左移位运算符,而是流提取运算符Stream \ extraction \ operators,它可以提取运算符右边的数据传给左边的对象,这看起来十分神奇,事实上,这得益于C++运算符重载的机制,这种机制我们后面将会讨论。总之std::cout对象将会帮我们把content输出到缓冲区,更灵活的,我们还可以这样:

std::cout << content1 << content2 ....

这其实也容易理解,因为我们都可以这样

int \ a = 1 << 2 << 3;

更有意思的,我们留在后面。

2. 输入: std::cin

        讲了std::cout,我们可以更容易理解std::cin,它与stdin相关联,这时我们需要叫做流插入的运算符Stream \ insertion \ operators,这将帮助我们将输入缓冲区的数据经过处理传给右边的对象。

3. 换行:std::endl

        endl \Rightarrow end \ line,简单来说,std::endl将向输出序列插入一个换行符并刷新它。

        Using std::endl is often inefficient, as it actually does two jobs: it outputs a newline (moving the cursor to the next line of the console), and it flushes the buffer (which is slow). If we output multiple lines of text ending with std::endl, we will get multiple flushes, which is slow and probably unnecessary.
        使用std::endl通常效率低下,因为它实际上做了两项工作:它输出一个换行符(将光标移动到控制台的下一行),并刷新缓冲区(这很慢)。如果我们输出多行以std::endl结尾的文本,我们将获得多次刷新,这很慢,可能没有必要。

        所以在一般情况下,可以简单使用‘\n’,当然这取决于个人。

三、C++的运行机制

        我们曾经在C部分,使用gcc手动编译一个C源程序,也谈过一个C源程序如何变为一个可执行程序,事实上这些在C++中也适用,只不过,我们需要使用g++。

g++ -c hello.cpp -o hello.o
g++ hello.o -o hello

(一)预处理

预处理阶段发生在编译之前,由预处理器完成。预处理器主要做以下几件事情:

  • 宏替换:处理源代码中的宏定义,将宏定义替换成实际的文本。
  • 条件编译:处理 #ifdef#ifndef#if#else#elif#endif 等指令,决定哪些代码会被编译。
  • 文件包含:处理 #include 指令,将指定的头文件内容插入到当前文件中。

预处理的结果是一个没有宏定义、条件编译指令和文件包含指令的新源文件。这个新文件被称为预处理后的源代码。

(二)编译

预处理之后,编译器会处理预处理后的源代码,将其转换成汇编代码或目标代码。这一过程涉及以下几个方面:

  • 词法分析:将源代码分解成一系列有意义的标记(tokens),如关键字、标识符、运算符等。
  • 语法分析:基于词法分析的结果,构造出抽象语法树 (AST),并检查语法错误。
  • 语义分析:检查类型是否匹配、变量是否已声明等语义问题。
  • 优化:在某些情况下,编译器会对生成的中间代码进行优化,以提高程序的性能。
  • 代码生成:将中间代码或优化后的代码转换为目标机器的目标代码。

编译的结果是一个或多个目标文件(.obj 或 .o 文件)。

(三)汇编

        如果编译器生成的是汇编代码而不是目标代码,那么就需要通过汇编器将汇编代码转换成目标代码。汇编器将汇编语言指令转换成二进制机器码。在现代编译器中,这一步骤通常是隐式的,因为编译器通常直接生成目标代码。

(四)链接

链接器的作用是将一个或多个目标文件以及所需的库文件组合成一个可执行文件。链接器完成以下任务:

  • 符号解析:确定外部符号的地址,即不同文件间相互引用的变量和函数的位置。
  • 重定位:更新目标文件中的地址,以便它们在内存中的位置能够正确地映射到可执行文件中的位置。
  • 合并代码段和数据段:将来自不同目标文件的代码段和数据段合并成单个段。

链接的结果是一个可执行文件。

四、Debug

        C 和 C++ 的底层设计理念可以概括为“信任程序员”——这既美妙又危险。C++ 旨在让程序员有高度的自由度来做他们想做的事。然而,这也意味着语言通常不会阻止你做没有意义的事情,因为它会假设你这样做是出于某种它不理解的原因。如果新程序员没有意识到,可能会陷入很多陷阱。这就是为什么知道在 C/C++ 中不应该做什么与知道你应该做什么一样重要的主要原因之一。

        所以,在一些情况下,C\C++程序所给出的程序结果可能不符合预期,甚至可能没有错误,你可能一脸茫然不知道为什么,这时候,你可能需要调试了 

(一)断点调试

        在 C++ 调试中,断点调试是一种常用且有效的方法。我们可以在关键代码行设置断点,例如在函数内部、循环条件处等。在Clion里面(在大部分IDE中也一样),单击行号一栏,打上断点(breakpoint)

        选择调试 

 

(二)单步调试

       接下来,通常我们将会有三种选择

  

Step Into (步入)

  • 当你在一个函数调用处设置了断点,或者程序已经停在了一个函数调用上时,使用“Step Into”命令会让程序进入该函数内部,逐行执行函数体内的代码。
  • 这个命令特别适用于想要检查一个函数内部的具体实现细节。

Step Over (跳过)

  • “Step Over”命令会使程序执行当前行的代码,但不会进入函数内部。如果当前行是一条函数调用,那么这条命令将会执行整个函数而不会停下来。
  • 这个命令特别适用于想要快速跳过一个函数而不关心它的内部实现。

Step Out (跳出)

  • “Step Out”命令会使程序执行到当前函数的结束,并返回到调用该函数的地方。
  • 这个命令特别适用于你已经进入了某个函数内部,但是想要直接回到上一层函数的执行位置。

Resume \ Program可以继续程序,直到遇到一个断点

(三)监视窗口

        在调试过程中,监视窗口是一个非常有用的工具。我们可以将关心的变量添加到监视窗口,实时查看其值的变化。这样可以帮助我们快速发现变量值的异常,从而定位问题。

        在Clion中,Threads \ \& \ varaibles会显示当前作用域的一些变量,在不同的IDE中也会有类似的机制

        在Memory \ View可以通过变量的地址查看其内存表示 

(四)输出调试信息

        通过在代码中使用 cout 或 printf 等输出函数,输出关键变量的值或提示信息,也是一种简单有效的调试方式。这可以让我们在程序运行过程中了解内部的状态和数据。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值