第二章:C++初探

从Hello Word出发了解C++

对于一门语言的入门,最直观的方法就是从一个Hello World程序出发,来了解这门语言的基本结构

#include 

int main(int argc,char ** argv) {
    std::cout << "Hello, World!" << std::endl;
    return 0;
}

从这个最简单的代码出发,我们可以引出C++中一个非常重要的定义函数。函数是一段能够被反复调用的代码,可以接收输入,进行处理并(或)产生输出,一个C++函数主要包括以下四个部分:

  • 返回类型:表示了函数返回结果的类型,可以为void,代表这个函数不需要显式的返回

    对于非void返回类型的函数来说,我们需要返回一个与返回类型匹配的返回值。虽然有时即使定一了非void的返回类型而不返回也可以正常编译,但是会出现警告信息,表明我们这么做是不规范的。

  • 函数名:用于函数调用,在程序中我们通过一个函数的名称来调用这个函数。

  • 形参列表:表示函数接收的参数类型列表,可以为空,可以为void,也可以只有形参类型而没有形参名称。当我们调用函数,传入的是实际参数,而系统在执行函数的时候会将我们传入的实际参数赋值给形式参数,从而使得我们对形式参数的操作与对实际参数的操作一致。

    在调用函数时候提供的实际参数数量和类型要与形式参数数量和类型一一对应。

    形参为void和为空的含义是一致的,都代表这个函数不需要传入参数。

    只有形参类型而没有形参名称的主要目的是为了设计统一的接口,同时又不希望传入的参数被使用而导致与预期不符的行为,例如下面的例子

    void fun(const char * pInfo, int ){
    std::cout << pInfo << std::endl;
    }
    
  • 函数体:包含函数的具体执行逻辑。

同时在C++程序中有一个特殊的函数mainmain函数作为整个程序的入口,由操作系统来进行调用,对于main函数需要注意以下两点

  • main函数的返回类型为int,表示程序的返回值,通常使用0来表示正常返回,其他的返回值可以由程序员自行定义用来表示针对不同情况的错误代码。这里的返回值会被返回给操作系统并可以从操作系统获得。同时main函数可以没有显式返回,在这种情况下根据标准,会默认返回0表示正常结束。
  • main函数有两种形参的定义方式:
    • int main(int argc,char ** argv)
    • int main()

接下来我们深入程序的内部来理解两个概念:类型和语句

  • 类型(内建):类型本身是由语言所定义的而不是由硬件所决定的概念,引入类型的主要目的是为一段储存空间赋予实际的含义。比如在我的电脑上int的大小为4个字节,那么就说明了我们可以用内存中连续的四个字节的空间来表示一个int
  • 语句:表明了程序需要执行的操作,语句主要分为以下几种类型
    1. 表达式加分号形成的单条语句。
    2. 通过使用{}包含多条语句来形成的语句块,对于块外的代码来说,语句块内的多条语句会被认为是一条语句。
    3. if/while等语句。

最后我们在大多数情况下需要对程序中的代码编写注释。注释是会被编译器忽略的内容,它主要用于编写说明或去除不使用的语句。在C++中注释主要有以下两种形式

  • /**/:块注释,注释符号内部包含的所有内容都会被忽略

    使用块注释要注意首末符号的匹配性质,每一个/*只会和最近的*/匹配

    块注释可以用作占位符,增强程序可读性,比如上面的无形参的例子

    void fun(const char * pInfo, int /* pValue */ )
    
  • //:行注释,注释符号后面包含的所有内容都会被忽略

系统I/O

系统I/O指的是操作系统提供的输入输出工具,用于实现与程序的交互,在C++中指的主要是iostreamiostream是标准库所提供的I/O接口,用于与用户交互。在使用iostream之前我们首先要引入对应的头文件

#include 

这里需要注意两点:

  • 头文件的引入既可以使用""也可以使用<>,两者的区别是优先查找头文件的位置不同。""会优先在当前目录下查找,而<>会优先在系统环境变量所定义的路径下查找。
  • 一般来说,我们自己编写的头文件都是以.h结尾的,但是所有的C++标准库的头文件都是不带结尾的。

系统I/O主要分为输入流和输出流两大类

  • 输入流:cin
  • 输出流:cout/cerr/clog

对于三种输出流,它们的主要区别为

  • 输出目标不同:coutcerr在输出到屏幕上时没有区别,但是可以通过重定位来实现分别输出到不同的文件中。
  • 是否立即刷新缓冲区:cerr会立即刷新缓冲区,主要目的是避免在错误信息被输出到缓冲区之后程序崩溃缓冲区被操作系统回收导致的的错误信息丢失。而clog不会立即刷新缓冲区,这样做的好处是可以利用缓冲区机制最大程度的减少与外部设备的交互次数,提高性能。

在有些情况下我们希望coutclog也立即刷新缓冲区,我们就可以使用以下的操作符

  • std::flush:刷新缓冲区
  • std::endl:刷新缓冲区并换行

这里我们需要注意,我们使用cout/clog的目的就是打印普通输出和日志输出,这两种类型的输出通常情况下并不要求实时显示,它们使用缓冲区机制的目的就是为了提升性能,如果我们在程序中大量使用std::flushstd::endl,那么就失去了缓冲区的意义。所以我们只有在必要的时候才会去强制刷新缓冲区。

最后,在C++程序中也可以使用C语言的系统I/O printf/scanf,它们和C++中的系统I/O cout/cin的特点各有各的特点:

  • printf/scanf:比较直观,可以一目了然地看出需要输出的语句的意思,但是格式化控制符需要用对,不然会产生不可预期的结果,比较容易出错。
  • cout/cin:比较智能,采用了范型编程的思想对于不同类型的输入有着很好的适配性,不容易出错,但书写冗长,不够直观。

命名空间

我们通常开发一个程序并不是一个人来开发,而是多人合作开发,那么每个人在自己负责的部分中就会使用一些自己喜欢的变量名称。这时如果两个人使用相同的名称就有可能发生命名冲突。为了解决这个问题,C++引入了命名空间机制。主要的使用方法如下

namespace NameSpace{
void fun();
}

如果不使用命名空间,默认输入全局命名空间。

访问命名空间中的变量由以下几种方法:

  • 域解析符::
    NameSpace::fun();
    
  • using 语句:使得命名空间中的变量全局可见
    using namespace NameSpace;
    fun();
    

    不推荐,使用命名空间的目的就是为了避免名字冲突,这么就相当于将命名空间中的所有名称都暴露给了之后的代码,失去了使用命名空间的意义,同时也污染了命名空间。尤其是用在头文件中极其危险!!!

  • 名字空间别名
    namespace ns = NameSpace;
    ns::fun();
    

所有的C++标准库都使用一个统一的命名空间std

命名空间的引入也导致了我们在编译阶段需要编译阶段对变量名称进行名字改编,以避免名字冲突。在整个程序中只有main函数的名字不会被改编,主要的原因是main在整个程序中一定是唯一的,不可能出现冲突。

控制流

接下来我们使用一个简单的猜数字程序来了解一下C++控制流的相关概念。首先我们来看一下分支选择的控制流

int main(int argc,char ** argv) {
    std::cout << "Please input a number: \n";

    int y = 0;
    std::cin >> y;
    if(y == 42){
    std::cout << "You are right! \n";
    }
    else{
        std::cout << "You are Wrong";
    }
}

在这里我们选择了实现分支选择的其中一种方法:if/else语句。if/else语句主要分为两大部分:

  • 条件部分:用于判断需要执行的部分
  • 语句部分:要执行的操作

在这里我们第一次碰到了条件判断语句y==42,其中出现了==这个新的操作,与我们之前使用的=相比,主要有以下区别:

  • =:用于赋值,将数值保存在变量所对应的内存中
  • ++:用于判断两个值是否相等

一定要注意这两个之间的区别,这里可以把常量放在左边来避免误用。

接下来我们对上述的程序进行修改,来引入循环执行的控制流的相关概念

int main(int argc,char ** argv) {
    int y = 0;
    while(y != 42){
        std::cout << "Please input a number: \n";
        std::cin >> y;
    }
}

在这里我们选择了实现循环执行的其中一种方法:while语句。while语句主要分为两大部分:

  • 条件部分:用于判断是否需要执行
  • 语句部分:要执行的操作

结构体与数据类型

我们可以把数据放在一起组织成一个结构体,来紧凑地表达更加复杂的数据类型,使数据之间的内聚性更强,比如如下用于表示点坐标的结构体

struct Point{
    int x;
    int y;
};

对于一个结构体,我们需要注意以下问题:

  • 可以通过.操作符来访问结构体的内部数据
  • 可以作为函数的输入参数或者返回类型
  • 可以引入成员函数,更好地表示函数与数据的相关性

你可能感兴趣的:(c++)