C++ 程序设计 第1章 C++简介

第1章 C++简介

1 C++语言的发展简史

1.1 了解C++语言的发展历史,识记层次

20世纪40年代计算机的面世,计算机程序设计语言也应运而生,从最早的机器语言到汇编语言,再到后来的各类高级语言。

在这众多的程序设计语言中,只是很少的一部分得到了广泛应用,C++语言可以说是其中最具代表性的一种。

通常根据是否可以直接操纵计算机底层硬件,将程序设计语言分为低级语言,中级语言和高级语言,机器语言和汇编语言属于低级语言一类。

高级语言是面向用户的语言,第一代高级语言以 Fortran 语言和 ALGOL60 语言为代表

C++语言于1979年在美国贝尔实验室开始设计开发的

1985年推出了C++ 1.0版。

1989年和1993年增加了很多新功能的C++2.0及C++3.0相继问世

1998年,C++的 ANSI/ISO 标准被投入使用,这是 C++ 标准第一版,称为 C++98,这个版本的C++被认为是标准C++。所有的主流C++编译器都支持这个版本的C++

C++标准第二版,第三版,第四版和第五版分别于2003年,2011年,2014年和2017年发布,分别被称为C++03,C++11,C++14,C++17,都是C++变成语言的正式标准。

本书程序都基于C++98

1.2 理解C++语言与C语言的关系,识记层次

C语言是C++语言的前身,再进一步扩充和完善C语言的基础上得到了C++语言。

C++语言最初被命名为带类的C语言

1983年更名为C++语言。

可以将C++语言看作事C语言的一个超集,C语言中的绝大部分语法成分都被保留下来。

事实上,标准的C程序也可以在C++语言的开发环境下运行

2 C++语言的特点

2.1 了解C++与C的主要异同点,识记层次

C++是一种编译式的,通用的,大小写敏感的编程语言,完全支持面向对象的开发模式。

C++特点

  1. 它是C语言的继承,尽量兼容C语言,既保持了C语言的简洁和高效,可以像C语言那样进行结构化程序设计,同时增强了C语言对类型的处理
  2. 加入了面向对象的特征,可以进行以抽象数据类型为特点的基于对象的程序设计,还可以进行以继承和多态为特点的面向对象的程序设计,虽然C++保留了C语言的语法绝大部分,但C++的程序结构与C语言的程序结构存在很大差别
  3. 应用领域更加广泛,比C语言更安全,支持过程化编程,面向对象编程和泛型编程
  4. 可运行于多种平台上,如Windows,MAC操作系统及UNIX的多种版本

2.2 掌握C++的基本输入/输出语句,熟悉程序的输入/输出方法,简单应用层次

C语言中,标准的键盘输入和屏幕输出功能分使用 scanf() 和 printf() 两个函数实现

在C++中,类库提供了输入流 istream 和输出流 ostream。cin和cout分别是istream类和ostream类的对象,用来实现基本的键盘输入和屏幕输出

提取操作:从输入流中获取数据的操作

插入操作:向输出流中添加数据的操作

>> << 是移位运算符,在C++类库提供的头文件中对 >><< 进行了重载,使之分别作为流提取运算符的流插入运算符,用来输入和输出C++标准类型的数据

C++中,使用流提取运算符 >> 从标准输入设备键盘取得数据,使用 cin 可以获得多个来自键盘的输入值

cin >> x;//从键盘获取参数并赋值给 x

cout 是一个标准输出流对象,使用 << 向屏幕输出信息

用户自定义的类型的数据,不能直接用 >> << 进行输入和输出,必须对 >><< 进行运算符重载后才可以使用

cin 语句中的变量可以是任何基本类型的变量,当连续从键盘读取想要的数据时,以空格,制表符或回车键作为分隔符

如果想把空格,制表符,回车键输入给字符变量,可以使用 getchar() 函数

cout 语句中的表达式可以是各种基本类型的变量,常量,以及有变量常量组合得到的正确表达式

2.3 掌握函数的声明,参数传递方式,参数默认值,内联函数等相关知识,并在程序中灵活运用,综合应用层次

参数默认值

在C++中,可以在声明函数时为形参指定默认值。当调用有默认参数值的函数时,调用语句中可以不给出对应的实参,这就相当于调用该函数时以默认值作为参数,指定默认值时不仅可以用常数,还可以用任何有定义的表达式作为参数的默认值,但不允许是函数内部定义的局部变量。

提供给默认值必须按从右往左的顺序提供,即有默认值的形参必须在形参列表的最后,如果某个形参没有默认值,则它左侧的所有形参都不能有默认值。或者说,在有默认值的形参的右侧,不能出现无默认值的形参。

在函数原型中,可以省略形参的名字,而只给出它的类型及默认值。这个是函数声明,不是函数定义。

函数声明

格式如下

类型标识符 函数名(参数列表);

带默认值的函数声明

void defaultvalue(int = 10,double = 20.0);
void defaultvalue(int a,int b,int c=20);

参数传递方式

在C++中,函数调用时参数的传递有两种方式:传值和传引用

传值,实际上时传递对象的值。传引用,是传递对象的首地址值。

如果函数的形参不是引用,则采用传值的方式,函数内部对形参的改变不影响函数外实参的值。调用一个函数时,他的形参及函数内部定义的局部变量被分配在称为栈的内存空间中,这与实参所在的空间不同,传值时,将实参的值拷贝到栈中对应的形参的地址中,函数内部对形参的操作,其实是对这个地址的操作,而不是对实参所占用的地址进行操作的。函数执行完毕后,并不将这个地址中的值拷贝出去,所以实参的值不受影响

如果函数的形参是引用,则采用传引用的方式,函数调用时,实参对象名传递给形参对象名,形参对象名就成为实参对象名的别名,即形参对应实参的引用,他们是等价的,代表同一个对象,也可以看作是将实参的地址传递给了形参。在函数内部对形参的操作,都是对这个地址的内容进行的,相当于对实参的值进行了操作。所以当函数执行完毕返回后,实参的变化被保留了下来。

可以使用 const 引用作为函数的参数,这样做既可以避免复制形参时所需要的开销,又能防止实参在函数执行过程中被改变

变量是可以作为赋值语句的左值存在的,既然引用可以看作是变量的别名,那么引用也可以作为赋值语句的左值

在赋值语句中,复制好左侧的表达式称为左值,右侧的表达式称为右值,左值表达式既能出现在赋值号的左侧,也能出现在赋值号的右侧

变量,指针,引用等都可以作为左值

内联函数

使用函数的目的之一是为了减少程序代码,实现程序代码的共享,从而提高程序的开发效率和可维护性。

但是调用函数时会带来程序运行时间上的开销,使得程序的执行效率降低。

如果一个函数体较大,那么调用函数及返回时所付出的代价,与代码共享所得到的回报相比,显得微不足道。特别是频繁调用时,函数调用导致的时间开销可能会使程序运行明显变慢。

为了避免这种频繁的函数调用与返回,C++引入了内联函数的概念。

使用内联函数,编译器在编译时并不生成函数调用,而是将程序中出现的每一个内调函数的调用表达式直接用该内联函数的函数体进行替换,就像整个函数体在调用处被重写了一遍一样

定义内联函数格式

inline 返回值类型 函数名(形参表){
    函数体
}

如果仅在声明函数原型时加上关键字 inline,并不能达到内联效果。

内联函数主要应用于代码量少的函数,编译时,编译程序将整个函数体的代码复制到调用该函数的位置,而不会编译成函数调用的指令。

内联函数不是在调用时发生控制转换,而是在编译时将函数体嵌入在每一个调用处。

如果函数体中有循环语句和switch语句则通常不定义为内联函数。事实上,如果将一个较复杂的函数定义为内联函数,大多数编译器会自动将其作为普通函数来处理。

因此,在函数定义中加上inline关键字仅仅是提示编译器,程序员希望函数是内联函数,编译器根据自己的处理方式可能忽略这一提示,而生成真正的函数调用。

2.4 理解函数重载的概念,识记层次

程序中经常会出现对不同类型的量进行同类操作的情况,面向对象程序设计语言的特点之一是允许函数的重载

函数重载:是指在程序的同一范围内声明几个功能类似的同名函数。

有了函数重载机制,编写程序时,可以对完成类似功能的不同函数统一命名,减少了命名空间的浪费

为了能有效区分它们,各函数的参数表不能完全相同。函数名加上参数表称为函数的签名。也就是说,重载的各个函数签名都是不同的,所以可以区分它们。

实现函数的重载必须满足两个条件之一

  1. 参数表中对应的参数类型不同
  2. 参数表中参数个数不同

不同类型参数的顺序不同也能实现函数重载,返回值类型不能用来区分函数,采用引用参数不能区分函数(基本类型和引用类型不能区分函数)

在有些情况下,调用语句中的实参与函数形参的类型不完全匹配,但存在赋值兼容的情况。此时,编译器也可以确定要调用的函数。比如,int类型和float类型都可以自动转为double类型,所以经过类型转换后找到了调用的函数

具有相同函数名的两个及以上的函数,其形参的个数或者类型不同,编译器根据实参和形参的类型及个数的最佳匹配,自动确认调用哪一个函数,这就是函数的重载及其调用

调用重载函数的二义性

在有函数重载的情况下,当进行类型转换时,可能会出现多个函数都匹配的情况,也就是函数调用出现了二义性。

在两个函数同名而参数个数不同,但其中参数多的那个函数的参数又可以取默认值的情况下,也可能引发二义性。这将导致编译失败

2.5 理解const的含义及作用,领会层次

const修饰其左值的内容,如果const是本行的第一个标识符,则它修饰其右侧的内容。

与C语言一样,C++也可以使用const限定访问权限,告诉编译器,它所修饰的是不能改变的,也就是不能作为左值使用。实际上,修饰指针左侧时,可以作为左值。

程序使用指针时,涉及的量有两个,一个是指针本身,一个是只指针所指向的内容。

const修饰指针时,基本含义如下:

  1. 如果唯一的const位于符号 * 的左侧,表示指针所指数据是常量,数据不能通过本指针改变,但可用其他方式进行修改,指针本身是变量,可以指向其他的内存单元。
  2. 如果唯一的const位于符号 * 的右侧,表示指针本身是常量,不能让该指针指向其他内存地址,指针所指向的数据可以通过本指针进行修改。
  3. 在符号 * 的左右各有一个const时,表示指针和指针所指向的数据都是常量,既不能让指针指向其他地址,也不能通过指针修改所指向的数据

2.6 进一步理解指针的概念,掌握C++中动态内存分配及释放的方法,简单应用层次

指针变量中保存的是一个地址,有时也称指针指向一个地址

数组的长度是声明数组时指定的,在整个程序运行过程中通常是不变化的,C++不允许定义元素个数不确定的数组

在程序设计中,经常会遇到需要根据待处理的数据量确定数组大小的情况。如果指定一个尽可能大的数组,要么空间浪费,要么容量不足

动态内存分配

在程序运行期间,根据实际需要,临时分配一段内存空间用于存储数据。这种内存分配是在程序运行期间进行的。与之对应的,在编译器就确定数组空间大小的方式可以称为静态内存分配

使用 new 运算符实现动态内存分配

T * p = new T;// p是类型为 T* 的指针,T 是任意类型

这样的语句会动态分配出一片大小为 sizeof(T) 的内存空间,并且将该内存地址的起始地址赋值给指针 p

int size = 5;
int *parr = new int[size*20];

//创建普通数组
string strs[3] = {"","",""};

使用new运算符动态申请的内存空间,需要在使用完毕释放,C++提供了 delete 运算符,用来释放动态内存

delete运算符基本用法

delete 指针;

delete 运算符后面的指针必须是指向动态分配的内存空间,否则运行时很可能会出错。

如果delete后接的不是动态指针,程序将会中止,后面的语句不会执行,vscode上控制台显示,free(): invalid pointer

释放new运算符创建的数组

delete []指针;

如果不写 [] ,而使用了delete 指针的方式释放,虽然编译时没有问题,但实际上会导致动态分配的数组没有被完全释放。

使用 new 运算符动态分配的内存空间,一定要用delete 运算符释放,否则,即使程序运行结束,这部分内存空间仍然不会被操作系统回收,从而成为被白白浪费掉的内存垃圾。这种现象被称为内存泄漏,当一个程序不停的进行动态内存分配但总是没有释放,那么可用内存就会越来越少,导致操作系统运行速度变慢,甚至影响到其他程序的运行。严重时必须重启计算机才能恢复。

使用指针时,必须要谨慎。当使用delete释放掉指针所指向的空间后,如果再通过指针访问这个空间,也得不到想要的结果。

动态数组不能通过sizeof获取数组的大小了

2.7 理解引用的基本概念,掌握引用的定义与使用,简单应用层次

引用:相当于给变量起了一个别名。变量对于某个内存地址,如果给某个变量起了别名,相当于变量和这个引用都对应到同一地址

定义格式:

类型名 &引用名 = 同类型的某变量名;

int oneInt;

int &aname = oneInt;

声明引用后,系统不为引用分配空间,引用和变量对应的是同一个内存地址,对于类型 T & 的引用和类型为 T 的变量是完全兼容的,可以相互赋值

  1. 声明引用时,变量必须已进行了初始化,在 C++98 标准下,系统自动将int类型变量初始化为 0。
  2. 不能有空引用,引用必须指向某个已存在的内存区域的首地址
  3. 不能申明引用的引用
  4. 不能用常量初始化引用
  5. 通常也不能用表达式初始化引用,除非该表达式的返回值是某个变量的引用
  6. 同一个变量的引用可以有多个

常引用,定义引用时,可以在定义的前面加 const 关键字,表明是常引用。不能通过常引用去修改其引用的变量,也就是不能再对常引用赋值了。

使用 const T 的常量和 const T & 类型的常引用不能直接用来初始化 T & 类型的引用,如果要赋值,则必须进行强制类型转换。

试验过了,只有引用类型不能直接被常量或者常引用初始化(必须强转),其他都能初始化。

在程序中不仅能定义变量的引用,还可以将引用用在函数中,引用既可以作为函数的参数使用,还可以作为函数的返回值使用。

2.8 掌握类string的基本功能,能够声明类string对象,能够调用类string中的成员函数进行基本操作,简单应用层次

C++标准模板库中提供了string数据类型,专门用于处理字符串。string是一个类,这个类型的变量称为string对象

声明string

要在程序中使用string对象,必须在程序中包含头文件string。

#include string

声明一个string对象,与声明普通变量时类似的

string 变量名;

既可以使用字符串常量进行初始化,也可以使用另一个字符串变量进行初始化。没有初始化的字符串变量的值是空串 "",还可以使用字符数组对string变量进行初始化,还可以声明一个string对象数组,数组中每个元素都是字符串。

计算string数组的元素个数 ,sizeof(string)的大小是 32 字节,sizeof(数组名),是数组的总长度,个数*32,不可以是动态内存数组。

sizeof(str) / sizeof(string)

前者是整个数组的大小,后者时一个string元素占用的大小,相除得到元素个数

string对象的基本功能

  1. string对象可以使用 cin 和 cout 进行输入和输出

  2. string对象之间可以互相赋值,也可以用字符串常量和字符串数组的名字对string对象赋值,赋值时不需要考虑被赋值的对象是否有足够的空间来存储字符串。

  3. string对象之间可以用 < <= == != >= > 运算符进行比较,大小的判定是按字典序进行的,而且是大小写相关的,比如,大写字母的ASCII码小于小写的ASCII码,所以 "Zbc" < "abc"

  4. 还可以使用运算符 + 进行连接,可以将string对象看作是字符数组,然后通过 [] 运算符和下标来存取字符串中的某个字符

string对象用法

string类中为字符串对象提供了一些成员函数,可以很方便地实现一些功能,如查找子串等

调用方法是 string对象名.成员函数名(参数)

const char *c_str() const;//返回一个属于字符串的指针,字符串内容与本string串相同
int size() const;//返回当前字符串的大小 STL新加的 和length实现一模一样
int length() const;//返回当前字符串的长度 C++沿用下来的 
bool empty() const;//判断当前字符串是否为空,size()==0
size_type find(const char *str,size_type index);//返回str在字符串中第一次出现的位置 从index开始查找,未找到返回-1
size_type find(char ch,size_type index);//返回字符在字符串中第一次出现的位置 从index开始查找,未找到返回-1
string &insert(int p,const string &s);//在p位置处插入字符串
string &append(const char *s);//将字符串s连接到当前字符串的结尾处
string substr(int pos=0,int n=npos) const;//返回从pos开始的n个字符组成的字符串

3 C++语言的程序结构

3.1 掌握C++程序结构,识记层次

C++程序以 .cpp 作为文件扩展名,文件中包含若干个类和若干个函数。程序中必须且只有一个主函数main(),这就是程序执行的总入口。主函数也称为主程序,程序从主函数的开始处执行,按照其控制结构,一直执行到结束。

程序的结束通常是遇到了以下两种情形之一

  1. 在主函数遇到return 语句
  2. 执行到主函数最后面的括号

主函数中可以调用程序中定义的其他函数,但其他函数不能调用主函数,主函数仅是系统为执行程式时调用的

C++程序中,扔沿用C语言的注释风格,两种形式,///* */

3.2 能够正确使用头文件及命名空间,识记层次

C语言中使用头文件保存程序中用到的声明,如函数声明,常量定义等,C++也是如此

iostream 是C++的标准输入/输出流,每条 #include 指令仅可以包含一个头文件。

在C++中,头文件不在以 .h 结尾,比如头文件 iostream 和 string

常用头文件

  1. <iostream> 标准输入输出流
  2. <fstream> 标准文件流
  3. <string> 标准字符串处理函数
  4. <cmath> 标准数学函数

使用尖括号括住系统提供的头文件,使用双引号括住自己定义的头文件

当使用尖括号时,C++编译器将首先在C++系统设定的目录中寻找要包含的文件,如果没有找到,再到指令中指定的目录去寻找

采用双引号时,C++编译器在用户当前目录下或指令中指定的目录下寻找要包含的文件。

#include "d:\admin\dev\cpp\first.h"

不同模块中出现的标识符之间有可能发生重名现象,这就会引起错误,C++中为了避免名字定义冲突,特别引入了 命名空间 的定义,即namespace,作用是为了消除同名引起的歧义

语法格式

namespace 命名空间名{
    命名空间内的各种申明(函数申明,类申明等)
}

C++标准库中的所有标识符都定义在一个名为std的命名空间中。

引入其他命名空间的标识符的语法格式

命名空间名::标识符名

using 命名空间名::标识符名 // 可以在本文件中直接使用该标识符

using namespace 命名空间名 // 可以在书本文件中直接使用指定命名空间中的所有标识符

posted @ 2023-03-20 02:11  快乐在角落里  阅读(87)  评论(0编辑  收藏  举报