C++复习

156 阅读14分钟

二 C++简单程序设计

  1. 全面兼容C:它保持了C的简洁、高效和接近汇编语言等特点;对C的类型系统进行了改革和扩充
  2. C++也支持面向过程的程序设计,不是一个纯正的面向对象的语言
类型使用范围
auto暂时存储在内存中
register存放在通用寄存器中
extern在所有函数和程序段中可引用
static内存中固定地址存放,在整个程序运行期间有效

符号常量

例:
const float pi=3.1415926;
或 float const pi=3.1415926; 

数据的输入和输出

>> 流提取符,<< 流插入符 预定义流类对象cin, cout

typedef语句

typedef 已有类型名 新类型名表;

联合体

与结构体的形式很像

三 函数

  1. 用函数计算两个数的最大公约数
1. 暴力轮询法
void MaxFactor(int n1, int n2)
{
    int gcd = 1;
    for(int i = 1; i < n1 && i < n2; ++i)
    {
        if(n1 % i == 0 && n2 % i == 0)
            gcd = i;
    }
    return gcd;
}
2. 两数相减, 未曾设想的道路
void MaxFactor(int n1, int n2)
{
    while(n1 != n2){
        if(n1 > n2)
            n1 -= n2;
        else
            n2 -= n1;
    }
    return n1;
}
3. 递归,更是未曾设想
void MaxFactor(int n1, int n2)
{
    if(n2 != 0)
        return MaxFactor(n2, n1%n2);
    else
        return n1;
}
  1. 用函数计算两个数的最小公倍数
1. 暴力轮询法
void MinMultiple(int n1,int n2)
{
    int gmd = (n1 > n2 ? n1 : n2);
    while(1)
    {
        if(gmd % n1 == 0 && gmd % n2 == 0)
            break;
        gmd++;
    }
    return gmd;
}
2. 利用最大公约数来求
最大公约数gcd,求法看上面
return ((n1*n2)/gcd)

内联函数

  1. 声明时使用关键字 inline。
  2. 编译时在调用处用函数体进行替换,节省了参数传递、控制转移等开销。
  3. 注意:

内联函数体内不能有循环语句和switch语句。

内联函数的定义必须出现在内联函数第一次被调用之前。

对内联函数不能进行异常接口声明。

带默认形参的函数

  • 调用出现在函数体实现之前时,默认形参值必须在函数原形中给出。
  • 调用出现在函数体实现之后时,默认形参值需在函数实现时给出。
如:
int add(int a = 5, int b = 6)
int main(){
   int a,b;
   while(cin >> a >> b)
       return add();
}
int add(int a,int b)
{
    return a+b;
}

又如
int add(int a = 5,int b = 6)
{
    return a+b;
}
int main(){
   int a,b;
   while(cin >> a >> b)
       return add();
}
  • 默认形参值必须从右向左顺序声明,并且在默认形参值的右面不能有非默认形参值的参数。因为调用时实参取代形参是从左向右的顺序。
int add(int x,int y=5,int z=6); //正确
int add(int x=1,int y=5,int z); //错误
int add(int x=1,int y,int z=6); //错误

重载函数

  • 必须是形参类型或者形参个数不同的同名函数
  • 编译器不以返回值来区分,不能只有返回值不同
  • 存在于父类、子类与同类之中

重写

  • 返回值、参数、方法名完全相同
  • 子类方法不能缩小父类方法的访问权限
  • 子类方法不能抛出比父类方法更多的异常
  • 存在于父类与子类之间
  • 方法被定义为final不能被重写

C++系统函数

头文件cmath

四 类与对象

类的声明

前向引用声明注意事项

  • 使用前向引用声明虽然可以解决一些问题,但它并不是万能的
  • 提供一个完整的类声明之前,不能声明该类的对象,也不能在内联成员函数中使用该类的对象。
  • 当使用前向引用声明时,只能使用被声明的符号,而不能涉及类的任何细节。
class Fred; //前向引用声明
class Barney {
   Fred x; //错误:类Fred的声明尚不完善
};
class Fred {
   Barney y;
};

编译的时候编译器报错

因为:Fred类没有定义

五 C++程序结构

作用域与可见性

  • 函数原型作用域:void Circlelen(int radius)里面的参数只在函数内部才有效
  • 块作用域:最熟悉的
  • 类作用域:通过类的访问来作用
  • 文件作用域:全局变量、静态变量

可见性:取小

生存周期

  • 静态生存周期:文件作用域、静态变量。随着程序的生命周期而生存
  • 动态生存周期:其他。开始于声明处,结束于作用域结束处

数据段共享和保护

###共享 静态变量:由类的所有对象共同保护

static 类型说明符 标识符

静态变量只能访问静态函数,访问非静态成员函数

保护

const常量

常量
格式: const 类型说明符 标识符
eg. const int number;
    int static const number;   // error!!  static和const不能同时使用

需要初始化,定义后不可以改变其值

常函数

不能将const 放在函数名前面,不然返回的是类型为const的函数

格式: 类型说明符 函数名 ([参数]) const{}
eg. void getNumber(int a , int b) const{}
错误:
    //number已经有初值
    void getNumber(int a , int b) const{number = 34;}
正确:
    void getNumber(int a , int b) const{return 34;}

不能进行改变初始量的操作

又如: Date 类中的三个常量成员函数:GetYear()、GetMonth() 和 GetDay()

class Date {
public:
    int GetYear() const { return y; }
   int GetMonth() const { return m; }
    int GetDay() const { return d; }
    void AddYear(int n); // add n years
private:
    int y, m, d; 
}; 

当在 GetYear() 中,修改成员变量 y 时,编译会报错

// error : attempt to change member value in const function
int Date::GetYear() const
{
    return ++y;
}        

注意 1):常量成员函数,在类外面实现时,const 后缀不可省略

// error : const missing in member function type
int Date::GetYear()
{
    return y;
}

注意 2):成员变量前有 mutable,则在常量成员函数中,也可修改成员变量

class Date {
// ...
private:
    mutable int y;
}; 
 
// ok, even changing member value in const function
int Date::GetYear()
{
    return ++y;
} 

六 指针、数组、字符串

一、 概念

  • 数组:数组是用于储存多个相同类型数据的集合。
  • 指针:指针相当于一个变量,但是它和不同变量不一样,他存放的是它变量在内存中的地址。

二、 赋值、存储方式、求sizeof、初始化等

  1. 同类型指针变量可以相互赋值,数组不行,只能一个一个元素的赋值或者拷贝
  2. 存储方式
  • 数组:数组在内存中是连续存放的,开辟一块连续的内存空间。

数组的存储空间不是在静态区,就是在栈上

  • 指针:指向存储内容的地址。

由于指针本身就是变量,再加上它所存放的也是变量,所以指针的存储空间不能确定

  1. sizeof
  • 数组:sizeof(数组名)/sizeof(数据类型)
  • 指针:32位的sizeof(指针名)都是4byte,64位的sizeof(指针名)都是8byte

this 指针

const 成员函数,可被 const 或 non-const 型类对象调用,如 GetYear(),而 non-const 成员函数,只能被 non-const 对象调用,如 AddYear()

void func(Date& d, const Date& cd)
{
    int i = d.GetYear();    // OK
    d.AddYear(1);        // OK
 
    int j = cd.GetYear();    // OK
    cd.AddYear(1);        // error
}   

任何成员函数,都有一个隐含的形参,即指向该类对象的 this 指针,它通常由编译器隐式的生成,通过 this 指针,类对象才能调用成员函数

一般的,this 指针默认指向 non-const 对象,因此,const 类对象,无法通过 this 指针调用 non-const 函数

// 默认的 this 指针,指向 non-const 对象
Date * const this; 

而 const 成员函数,通过参数后的 const 后缀,修改其 this 指针指向 const 型对象,此时,const 对象便可调用 const 成员函数

// 常量成员函数中的 this 指针,指向 const 对象
const Date * const this;

const 接口

上文的 func() 函数中,形参用 const 来修饰,可保证数据传递给函数,而本身不被修改,是设计 "接口" 的一种常用形式

void func(Date& d, const Date& cd)
{
   // ... ... 
} 

特殊情况下,想要 const 对象调用 non-const 成员函数,则可用 const_cast 移除对象的 const 属性,具体形式为: const_cast(expression)

void func(Date& d, const Date& cd)
{
    int j = cd.GetYear();    // OK
    const_cast<Date&>(cd).AddYear(1); // const 对象调用 non-const 成员函数
} 

这种做法,破坏了用 const 来指定 "接口" 的本意,并不推荐

友元

class A{
public A(int a){};
private: 
    int n,m;
friend void func1();
friend class B
};

void func1(){};

class B{};

友元破坏了类的封装性和数据的私密性

六 数组和指针

  • 常量指针:也被称为指向常量的指针,const T* ptr不能通过指针来改变所指对象的值,但指针本身可以改变,可以指向另外的对象。
int x=5,y=10;
const int * iptr=&x;  //指向常量的指针
*iptr=7; // 错误,不能通过指针修改所指对象的值
 x=7;  //正确,x的值可以改变
iptr= &y;  //正确,ipt的本身的值可以改变
  • 指针常量:也称为指针类型的常量,T* const ptr 指向的地址不能修改,指向内容可以修改
  int x = 5,y = 10;
  int *const iptr=&x; 
  *iptr = 7; // OK!
  iptr = &y; // wrong!
  • 指向常量的指针常量: const T* const ptr

数组名F是常量,是数组F的首地址

指针

普通变量不能用void类型,但是指针可以用void类型

void number; //error
void * numb; // ok

给指针赋值

格式一:先声明,再赋值   
类型说明符 *标识符;    
标识符 = &值;

格式二:直接定义   
类型说明符 *标识符 = &值;
int a,b;
int *pa,*pb=&b; 
pa=&a;

声明与赋值

例:int a[10],   *pa;
    pa=&a[0]; 或 pa=a;

通过指针引用数组元素 经过上述声明及赋值后:

*pa就是a[0],*(pa+1)就是a[1],... ,*(pa+i)就是a[i].
a[i], *(pa+i), *(a+i), pa[i]都是等效的。
不能写 a++,因为a是数组首地址是常量。

指针型函数和指向函数的指针

指针函数返回指针,而函数指针存储函数的地址。

类型说明符 *函数名 (参数列表);//指针型函数
    int * getArray(int n){
     static int *a = new int [n];
     for(int i=0;i<n;i++)
        a[i]=2*i;
     return a;
}
                          
int main()
{
    int num = 5;
    int* b = getArray(num); 
    for(int i=0;i<num;i++)
        cout<<b[i]<<endl;
}
相当于把*a赋值于*b
    
类型说明符 (*函数指针名)(参数列表);//函数指针
函数指针名 = 函数名
    void (*funPtr)(float);
    void printFloat(float F){};
    funPtr = printFloat;
    funPtr(pi)

加上类的运用之后,会出来一系列的比如:通过对象指针访问成员数据,通过对象指针访问成员函数,通过对象名访问成员,通过类指针访问成员

int (*Point::Pointptr)() = &Point::func1;
Point A(1,2);
Point *ptr = &A;
cout << (A.*Pointptr)() << endl;
cout << (ptr->func1)(); /对象指针名->函数名

指向类的非静态成员指针

  • 指向对象成员的指针也要先声明,再赋值,然后引用。
声明指向对象成员的指针
声明指向公有数据成员的指针
类型说明符  类名::*指针名;
  • 声明指向公有函数成员的指针
类型说明符  (类名::*指针名)(参数表);
对数据成员指针的赋值:
指针名=&类名::数据成员名
对数据成员指针的引用:通过对象名(或对象指针)与成员指针结合来访问数据成员
          对象名.* 类成员指针名
或: 对象指针名->*类成员指针名
对成员函数指针的赋值:
    指针名=&类名::成员函数名;
对成员函数指针的引用:必须通过对象调用非静态成员函数
             (对象名.* 类成员指针名)(参数表)
    或: (对象指针名->类成员指针名)(参数表)

#include<string>和#include<cstring>

  • string头文件可以进行+ - += -=操作
  • cstring包含一些C语言中对于字符串的操作:strcmp, strlen,strcpy等

七 继承与派生

派生的几种方式

  1. 如果不显式给出,系统默认是private
  2. 派生类继承的过程
    • 吸收基类成员:吸收除构造函数和析构函数的所有成员函数和数据
    • 改造基类成员:访问控制、同名隐藏
    • 添加新的成员:派生类的构造函数和析构函数
  • 公有继承:public保持public,protected保持protected,private无法访问

class B: public class A,派生类成员能够访问前两者,类外部只能访问public

  • 私有继承:所有成员保持private,父类中的private无法继承

class B: private class A派生类内部成员可以访问来自父类的public和protected,类的外部无法访问任何成员

  • 保护继承:所有成员保持protected, private属性成员无法继承

class B: protected class A派生类成员能够访问public和protected,类外部不能访问任何成员

构造函数和析构函数调用顺序

  1. 如果基类声明了带有形参表的构造函数时,派生类就需要声明自己的构造函数,保证基类进行初始化时能够获得必要的数据。
  2. 构造派生类的对象时,要对基类数据成员、新增数据成员和成员对象的数据进行初时化。
  3. 在构造派生类的对象时, ①调用基类的构造函数 ② 隐含调用内嵌对象成员的构造函数 ③ 执行派生类构造函数的函数体。

派生类名::派生类名(参数总表):基类名1(参数表1), 基类名2(参数表2), ...基类名n(参数),内嵌对象名1(内嵌对象参数表1),…内嵌对象名m(内嵌对象参数表m

  1. 调用基类构造函数,调用顺序按照它们被继承时声明的顺序(从左向右)。
  2. 调用成员对象的构造函数,调用顺序按照它们在类中声明的顺序。
  3. 派生类的构造函数体中的内容。

image.png CBAFDE

image.png

复制构造函数

若建立派生类对象时调用默认拷贝构造函数,则编译器将自动调用基类的默认拷贝构造函数。
若编写派生类的拷贝构造函数,则需要为基类相应的拷贝构造函数传递参数。例如:
C::C(C &c1):B(c1)
{…}

类型兼容

  1. 派生类对象继承基类
类型兼容规则是指在需要基类对象的任何地方,都可以使用公有派生类的对象来代替:
    
派生类的对象可以隐含转换为基类对象。
    
派生类的对象可以初始化基类对象的引用。
    
派生类对象的地址可以赋给指向基类的指针。
    
在替代之后,派生类对象可以作为基类的对象使用,但只能使用从基类继承的成员。

image.png

抽象类

  • 定义:类当中,如果有virtual的成员数据或者成员函数,那么这个类是虚类,虚类不能实例化,需要被继承后的非虚类来覆盖函数
  • 虚函数是动态绑定的函数
  • 构造函数不能是虚构函数,析构函数可以是虚构函数
  • 何时需要虚析构函数?通过基类指针删除派生类对象时

const和virtual可以同时使用

virtual double area() const {return c*c*3.14;}

纯虚函数

写法:

virtual int func1()=0;一定要被重写

虚基类

  1. 防止产生歧义,在继承的过程中加上一个virtual
  2. 在第一级继承时,就要把共同基类设置为虚基类
  3. 在建立对象时,只有最远派生类的构造函数调用虚基类的构造函数,该派生类的其他基类对虚基类构造函数的调用被忽略。

八 多态

  1. 多态分为四种类型:重载多态、强制多态、包含多态、参数多态
  2. 静态绑定和动态绑定:
    • 绑定过程出现在编译阶段,用对象名或者类名来限定要调用的函数**(重载、强制、参数)**
    • 绑定过程工作在程序运行时执行,在程序运行时才确定将要调用的函数**(包含多态)**
  3. 一般有virtual就是动态绑定
静态绑定
    double area(){};
动态绑定
    virtual double area(){};