学堂在线《C++语言程序设计基础》笔记

工作后大部分时间使用python开发,C++搁置了一段时间,最近新项目又开始使用C++。正好利用业余时间再加深对基础知识的理解。本文是学堂在线国家精品课程郑莉教授的《C++语言程序设计基础》的简要笔记。

课程地址:《C++语言程序设计基础》

随堂练习代码:

第一章 绪论

C++语言

  • 是高级语言
  • 支持面向对象的观点和方法
  • 将客观事物看做对象
  • 对象间通过消息传送进行沟通
  • 支持分类和抽象

面向过程的程序设计方法:

  • 机器语言、汇编语言、高级语言都支持;
  • 最初的目的:用于数学计算;
  • 主要工作:设计求解问题的过程。
  • 大型复杂的软件难以用面向过程的方式编写

面向对象的程序设计方法:

  • 由面向对象的高级语言支持;
  • 一个系统由对象构成;
  • 对象之间通过消息进行通信。

面向对象的基本概念

面向对象的基本概念

对象

  • 一般意义上的对象:现实世界中实际存在的事物。
  • 面向对象方法中的对象:程序中用来描述客观事物的实体。

抽象与分类

  • 分类依据的原则——抽象;
  • 抽象出同一类对象的共同属性和行为形成类;
  • 类与对象是类型与实例的关系。

封装

  • 隐蔽对象的内部细节;
  • 对外形成一个边界;
  • 只保留有限的对外接口;
  • 使用方便、安全性好。

继承

  • 意义在于软件复用;
  • 改造、扩展已有类形成新的类。

多态

  • 同样的消息作用在不同对象上,可以引起不同的行为。

程序的开发过程

程序的开发过程

程序

  • 源程序:
    • 用源语言写的,有待翻译的程序;
  • 目标程序:
    • 源程序通过翻译程序加工以后生成的机器语言程序;
  • 可执行程序:
    • 连接目标程序以及库中的某些文件,生成的一个可执行文件;
    • 例如:Windows系统平台上的.EXE文件。

三种不同类型的翻译程序

  • 汇编程序:
    • 将汇编语言源程序翻译成目标程序;
  • 编译程序:
    • 将高级语言源程序翻译成目标程序;
  • 解释程序:
    • 将高级语言源程序翻译成机器指令,边翻译边执行。

C++程序的开发过程

  • 算法与数据结构设计;
  • 源程序编辑;
  • 编译;
  • 连接;
  • 测试;
  • 调试。

计算机中的信息与存储单位

计算机中的信息与存储单位

计算机的基本功能

  • 算术运算;
  • 逻辑运算。

计算机中信息:

  • 控制信息——指挥计算机操作;
  • 数据信息——计算机程序加工的对象。
01a45e90678c2e5ed31ce1732b65ca8.png

信息的存储单位

  • 位(bit,b):数据的最小单位,表示一位二进制信息;
  • 字节(byte,B):八位二进制数字组成(1 byte = 8 bit);
  • 千字节    1 KB = 1024 B;
  • 兆字节    1 MB = 1024 K;
  • 吉字节    1 GB = 1024 M。

计算机的数字系统

计算机的数字系统

计算机的数字系统

  • 二进制系统;
  • 基本符号:0、1。

程序中常用的数制:

1573874625(1).png

R 进制转换为十进制:

各位数字与它的权相乘,其积相加,例如:

 (11111111.11)2 =1×27+1×26+1×25+1×24+1×23+1×22+1×21+1×20+1×2-1+1×2-2 =(255.75)10

十进制整数转换为R 进制整数:

“除以R取余”法。

1.png所以 6810=10001002

十进制小数→ R 进制小数:

“乘 以R 取整”法。

2.png所以 0.312510  = 0.01012

二、八、十六进制的相互转换

  • 1位八进制数相当于3位二进制数;
  • 1位十六进制数相当于4位二进制数,例如:
    • (1011010.10)2=(001 011 010 .100)2=(132.4)8
    • (1011010.10)2=(0101 1010 .1000)2=(5A.8)16
    • (F7)16=(1111 0111)2=(11110111)2

数据的编码表示

数据在计算机中的编码表示

二进制数的编码表示

  • 需要解决的问题:负数如何表示?
  • 最容易想到的方案:
    • 0:表示“+”号;
    • 1:表示“-”号。
  • 原码
    • “符号──绝对值”表示的编码
    • 例如:
3.png
  • 原码的缺点:
    • 零的表示不惟一
      • [+0] =000…0
      • [-0] =100…0
    • 进行四则运算时,符号位须单独处理,运算规则复杂。
  • 补码
    • 符号位可作为数值参加运算;
    • 减法运算可转换为加法运算;
    • 0的表示唯一。
  • 补码的原理
    • 模数:
      • n位二进制整数的模数为 2
      • n位二进制小数的模数为 2。
    • 补数:
      • 一个数减去另一个数(加一个负数),等于第一个数加第二个数的补数,例(时钟指针): 8+(-2)=8+10 ( mod  12 )=6;
      • 一个二进制负数可用其模数与真值做加法 (模减去该数的绝对值) 求得其补码,例(时钟指针):-2+12=10。
  • 补码的计算
    • 借助于“反码”作为中间码;
    • 负数的反码与原码有如下关系:
      • 符号位不变(仍用1表示),其余各位取反(0变1,1变0),例如:
        X=-1100110    [X] =1 1100110    [X] =1 0011001
    • 正数的反码与原码表示相同,正数的补码与原码相同;
    • 反码只是求补码时的中间码;
    • 负数的补码由该数反码的末位加 1 求得。
    • 对补码再求补即得到原码。
  • 补码的优点:
    • 0的表示唯一;
    • 符号位可作为数值参加运算;
    • 补码运算的结果仍为补码。

第二章 C++简单程序设计

符号常量

  • 常量定义语句的形式为:
    • const 数据类型说明符  常量名=常量值;
    • 或:
    • 数据类型说明符  const  常量名=常量值;
  • 例如,可以定义一个代表圆周率的符号常量:
    • const float PI = 3.1415926;
  • 符号常量在定义时一定要初始化,在程序中间不能改变其值。

第三章 函数

内联函数

  • 声明时使用关键字 inline。
  • 编译时在调用处用函数体进行替换,节省了参数传递、控制转移等开销。
  • 注意:
    • 内联函数体内不能有循环语句和switch语句;
    • 内联函数的定义必须出现在内联函数第一次被调用之前;
    • 对内联函数不能进行异常接口声明。

第四章 类

委托构造函数

类中往往有多个构造函数,只是参数表和初始化列表不同,其初始化算法都是相同的,这时,为了避免代码重复,可以使用委托构造函数。

Clock类的两个构造函数:

Clock(int newH, int newM, int newS) : hour(newH),minute(newM),  second(newS)  {         //构造函数}

Clock::Clock(): hour(0),minute(0),second(0) { }//默认构造函数

委托构造函数

委托构造函数使用类的其他构造函数执行初始化过程

例如:

Clock(int newH, int newM, int newS):  hour(newH),minute(newM),  second(newS){}

Clock(): Clock(0, 0, 0) { }

复制构造函数定义

复制构造函数是一种特殊的构造函数,其形参为本类的对象引用。作用是用一个已存在的对象去初始化同类型的新对象。

class 类名 {
public :

    类名(形参);//构造函数

    类名(const  类名 &对象名);//复制构造函数

    //       ...

};

类名::类( const  类名 &对象名)//复制构造函数的实现

{    函数体    }

复制构造函数被调用的三种情况

  • 定义一个对象时,以本类另一个对象作为初始值,发生复制构造;
  • 如果函数的形参是类的对象,调用函数时,将使用实参对象初始化形参对象,发生复制构造;
  • 如果函数的返回值是类的对象,函数执行完成返回主调函数时,将使用return语句中的对象初始化一个临时无名对象,传递给主调函数,此时发生复制构造。

第五章 数据的共享与保护

类的友元

友元是C++提供的一种破坏数据封装和数据隐藏的机制。

通过将一个模块声明为另一个模块的友元,一个模块能够引用到另一个模块中本是被隐藏的信息。

可以使用友元函数和友元类。

为了确保数据的完整性,及数据封装与隐藏的原则,建议尽量不使用或少使用友元。

类的友元关系是单向的

如果声明B类是A类的友元,B类的成员函数就可以访问A类的私有和保护数据,但A类的成员函数却不能访问B类的私有、保护数据。

共享数据的保护

  • 对于既需要共享、又需要防止改变的数据应该声明为常类型(用const进行修饰)。
  • 对于不改变对象状态的成员函数应该声明为常函数

常类型

  • 常对象:必须进行初始化,不能被更新。
    • const 类名 对象名
  • 常成员
    • 用const进行修饰的类成员:常数据成员和常函数成员
  • 常引用:被引用的对象不能被更新。
    • const  类型说明符  &引用名
  • 常数组:数组元素不能被更新(详见第6章)。
    • 类型说明符  const  数组名[大小]…
  • 常指针:指向常量的指针(详见第6章)。

常对象

用const修饰的对象

class A

{

  public:

    A(int i,int j) {x=i; y=j;}

                     ...

  private:

    int x,y;

};

A const a(3,4); //a是常对象,不能被更新

常成员

用const修饰的对象成员

 常成员函数

  • 使用const关键字说明的函数。
  • 常成员函数不更新对象的数据成员。
  • 常成员函数说明格式:
    • 类型说明符  函数名(参数表)const;
    • 这里,const是函数类型的一个组成部分,因此在实现部分也要带const关键字。
    • const关键字可以被用于参与对重载函数的区分
  • 通过常对象只能调用它的常成员函数。

 常数据成员

使用const说明的数据成员。

#include<iostream>

using namespace std;

class R {

public:

  R(int r1, int r2) : r1(r1), r2(r2) { }

  void print();

  void print() const;

private:

  int r1, r2;

};

 

void R::print() {

  cout << r1 << ":" << r2 << endl;

}

void R::print() const {

  cout << r1 << ";" << r2 << endl;

}

int main() {

  R a(5,4);

  a.print(); //调用void print()

  const R b(20,52); 

  b.print(); //调用void print() const

       return 0;

}

#include <iostream>

using namespace std;

class A {

public:

       A(int i);

       void print();

private:

       const int a;

       static const int b;  //静态常数据成员

};

 

const int A::b=10;

A::A(int i) : a(i) { }

void A::print() {

  cout << a << ":" << b <<endl;

}

int main() {

//建立对象a和b,并以100和0作为初值,分别调用构造函数,

//通过构造函数的初始化列表给对象的常数据成员赋初值

  A a1(100), a2(0);

  a1.print();

  a2.print();

  return 0;

}

常引用

  • 如果在声明引用时用const修饰,被声明的引用就是常引用。
  • 常引用所引用的对象不能被更新。
  • 如果用常引用做形参,便不会意外地发生对实参的更改。常引用的声明形式如下:
    • const  类型说明符  &引用名;
#include <iostream>

#include <cmath>

using namespace std;

class Point { //Point类定义

public:          //外部接口

       Point(int x = 0, int y = 0)

    : x(x), y(y) { }

       int getX() { return x; }

       int getY() { return y; }

       friend float dist(const Point &p1,const Point &p2);

private:         //私有数据成员

       int x, y;

};

 

float dist(const Point &p1, const Point &p2) {

       double x = p1.x - p2.x; 

       double y = p1.y - p2.y;

       return static_cast<float>(sqrt(x*x+y*y));

}

 

int main() {  //主函数

       const Point myp1(1, 1), myp2(4, 5);    

       cout << "The distance is: ";

       cout << dist(myp1, myp2) << endl;

       return 0;

}

第六章 数组、指针与字符串

注意:

  • 如果不作任何初始化,内部auto型数组中会存在垃圾数据,static数组中的数据默认初始化为0;
  • 如果只对部分元素初始化,剩下的未显式初始化的元素,将自动被初始化为零;

指针的概念、定义和指针运算

内存空间的访问方式

  • 通过变量名访问
  • 通过地址访问

指针的概念

  • 指针:内存地址,用于间接访问内存单元
  • 指针变量:用于存放地址的变量

指针变量的定义

  • 例:
static int i;

static int* ptr = &i;
  • 例:

*ptr = 3;

与地址相关的运算——“*”和“&”

  • 指针运算符
  • 地址运算符:&

指针变量的初始化

  • 语法形式

存储类型 数据类型 *指针名=初始地址;

  • 例:int *pa = &a;
  • 注意事项
    • 用变量地址作为初值时,该变量必须在指针初始化之前已声明过,且变量类型应与指针类型一致。
    • 可以用一个已有合法值的指针去初始化另一个指针变量。
    • 不要用一个内部非静态变量去初始化 static 指针。

指针变量的赋值运算

  • 语法形式

指针名=地址

注意:“地址”中存放的数据类型与指针类型必须相符

  • 向指针变量赋的值必须是地址常量或变量,不能是普通整数,例如:
    • 通过地址运算“&”求得已定义的变量和对象的起始地址
    • 动态内存分配成功时返回的地址
  • 例外:整数0可以赋给指针,表示空指针。
  • 允许定义或声明指向 void 类型的指针。该指针可以被赋予任何类型对象的地址。

例: void *general;

指针空值nullptr

  • 以往用0或者NULL去表达空指针的问题:
    • C/C++的NULL宏是个被有很多潜在BUG的宏。因为有的库把其定义成整数0,有的定义成 (void*)0。在C的时代还好。但是在C++的时代,这就会引发很多问题。
  • C++11使用nullptr关键字,是表达更准确,类型安全的空指针

指向常量的指针

  • 不能通过指向常量的指针改变所指对象的值,但指针本身可以改变,可以指向另外的对象。
 int a;

const int *p1 = &a; //p1是指向常量的指针

int b;

p1 = &b; //正确,p1本身的值可以改变

 *p1 = 1; //编译时出错,不能通过p1改变所指的对象

指针类型的常量

  • 若声明指针常量,则指针本身的值不能被改变。
int a;

int * const p2 = &a;

p2 = &b; //错误,p2是指针常量,值不能改变

动态内存分配

动态申请内存操作符 new

  • new 类型名T(初始化参数列表)
  • 功能:在程序执行期间,申请用于存放T类型对象的内存空间,并依初值列表赋以初值。
  • 结果值:成功:T类型的指针,指向新分配的内存;失败:抛出异常。

释放内存操作符delete

  • delete 指针p
  • 功能:释放指针p所指向的内存。p必须是new操作的返回值。

分配和释放动态数组

  • 分配:new 类型名T [ 数组长度 ]
    • 数组长度可以是任何表达式,在运行时计算
  • 释放:delete[] 数组名p
    • 释放指针p所指向的数组。
      p必须是用new分配得到的数组首地址。

浅层复制与深层复制

  • 浅层复制
    • 实现对象间数据元素的一一对应复制。
  • 深层复制
    • 当被复制的对象数据成员是指针类型时,不是复制该指针成员本身,而是将指针所指对象进行复制。

更多内容,请学习课程…

留下评论