C++基础语法

本学期的工程实践基础课选修了C++,因为要考试,顺便把以前学的C盲点记录一下。这些都是C的基础知识,涉及一些语法,以及程序设计中易犯的错误。不是概括的包含所有知识点,这只是针对我个人的盲点…

2 从C到C++

2.1 命名空间

使用命名空间(namespace)时,结尾处不能添加分号。

2.2 操作符

使用不带参数的操作符,如**(hex, endl),必须包含头文件iostream**;而使用带参数的操作符,如**(setfill,setw),则需要头文件iomanip**。

setw操作符,一次设置,一次有效;而其他操作符,一旦设置,一直有效,持续到下次被设置为止。

fixed之后再加setprecision(n),使得小数部分精确到n位。

2.3 强制类型转换

  • static_cast: 最常用的强制类型转换,将一种数据类型转换到另一种数据类型,并使用任何合理的方式。
  • const_cast: 强制去掉常量属性。
  • reinterpret_cast: 可用来改变指针类型,需谨慎使用。
  • dynamic_cast: 使用与继承层次中的类型转换(用于多态)。

2.4 string

函数getline()常用来读入一行到string类型的变量中,但它不会把换行符存入到这个变量中

  • s1.find_first_of(s2): 返回s1和s2都具有的第一个字符的索引。
  • s1.find_first_not_of(s2): 返回在s1但不在s2中的首位字符的索引。

2.5 内联函数

避免函数调用的开销,但如果函数体很大,那么程序的可执行代码将变得很大。宜将频繁使用的小函数声明为内联函数。
关键字inline出现在函数声明,而不是函数定义中。

2.6 函数签名与默认参数

返回值不是函数签名的一部分,所以不能通过返回值类型的不同,来实现函数的重载。默认参数出现的次序,应该从右往左,且不能间断。默认参数应出现在函数的声明而非函数的定义中给出。

3 类

3.1 默认构造函数

摘自CSDN一位大牛的解释 :

1
The default constructor is used whenever we define an object but do not supply an initializer. A constructor that supplies default arguments for all its parameters also defines the default constructor.

默认构造函数(缺省构造函数)跟系统提供不提供构造函数没有任何关系,默认构造函数就是当你定义一个对象时不需要提供初始化的的构造函数。包括三种情况:

  • 1、根本没有显式的定义构造函数,当然由系统提供的默认构造函数,这个构造函数没有参数,啥也不做
  • 2、定义了构造函数,但是不带任何参数,这也叫默认构造函数,特别的,如果这个函数体什么都不执行,就跟情况1一样。
  • 3、定义了构造函数,也带有参数,但是所有参数都有默认参数,这个也叫默认构造函数

当然在一个类中,以上3中情况不可能共存,只要有一点满足就是默认构造函数。

3.2 拷贝构造函数

  • 拷贝构造函数可以有多于一个的参数,但第一个以后的参数都必须有默认值。
  • 拷贝构造函数的第一个参数,必须是一个类对象的引用。如果一个类包含指向动态存储空间的指针类型的数据成员,则应该为这个类设计拷贝构造函数。
  • 如果拷贝构造函数是私有的,顶层函数和其他类的成员函数就不能通过传值来传递和返回该类的对象,因为这两个操作都需要调用拷贝构造函数。

3.3 构造函数

常量成员只能在初始化列表中初始化,这是初始化const类型成员的唯一方法。

3.4 析构函数

析构函数不带参数,不能被重载。

3.5 类数据成员与成员函数

  • 在类内部声明的static数据成员必须在任何程序块之外定义。
  • 如果将成员函数内的某个局部变量定义为静态变量,该类的所有对象在调用这个成员函数时将共享这个变量。
  • static类成员函数只能访问static类型的成员。

4 继承

4.1 改变访问限制

某些基类的成员可能不适合子类,这时候需要在子类中把基类的成员给隐藏掉。可以使用using声明。如下所示:

1
2
3
4
5
6
7
8
9
class BC {
public:
void setX(){}
};

class DC: public BC {
private:
using BC::setX;
};

4.2 保护成员

  • 保护成员和私有成员类似,只在该类中可见。但在共有继承下,保护成员在子类中是可见的,而私有成员不可见。
    在派生类的对象中都是不可见的。
  • 一般来说,避免将数据成员设计成保护成员,即使某个数据成员可以成为保护成员。但更好地解决办法是:首先将这个数据成员定义为私有成员,然后为它设计一个用来进行存取访问的保护成员函数,通常将这种类型的成员函数成为访问函数。

4.3 子类的构造函数

  • 如果基类拥有构造函数但没有默认构造函数,那么子类的构造函数应该显式的调用基类的构造函数。
  • 构造的顺序: 先父母、再客人、最后自己。举个例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
class A {
};

class B {
};

class C: public A {
public:
C() { }
private:
B b1;
B b2;
};

C类的构造函数,先构造父母A,在构造客人b1,b2,最后构造自己。子类的构造函数,只负责前一层对象的构造,祖先类对象的构造并不执行。

4.4 多继承的构造函数

先继承的先被构造,跟初始化列表中的顺序无关。客人不止一个,先声明谁就先构造谁。

4.5 保护继承

  • 基类中的所有公有成员在派生类中是保护成员。
  • 基类中的所有保护成员在派生类中是保护成员。
  • 基类中的所有私有成员在派生类中是不可见的。

5 多态

5.1 动态绑定

使用动态绑定的程序会影响效率,因为虚成员函数表需要额外的存储空间

5.2 构造函数与析构

构造函数不能是虚成员函数,析构函数可以是虚成员函数。

5.3 虚成员函数

只要非静态成员才可以是虚成员函数。

5.4 重载、覆盖、隐藏

  • 重载:具有不同的函数签名才可以重载,前提函数名要相同。
  • 覆盖:基类有一个与子类相同函数名的虚成员函数,函数签名要相同,才形成覆盖。
  • 隐藏:子类有一个与基类函数名相同的非虚成员函数,不论函数签名是否相同,子类都会隐藏基类的同名函数。

5.5 多态条件

继承层次中的同名函数,必须具有相同的函数签名,也就是所说的覆盖。

5.6 抽象基类

一个类只要有一个纯虚函数,就是抽象基类。抽象基类可以有其他不是纯虚成员函数或数据成员。

5.7 多态中的dynamic_cast

dynamic_cast操作是否正确与转型的目标类型是否多态无关,但转型的源类型必须是多态的。也就是说dynamic_cast只能施加于具有多态性的类型,而且转型的目的类型必须是指针或者引用。

6 操作符重载

6.1 重载规则

  • 成员选择符(.)、成员对象选择符(.*)、域解析操作符(::)和条件操作符(?:)不能被重载。除了赋值操作符(=)之外,基类中的所有被重载的操作符都将被子类继承。
  • 重载不改变操作符的优先级和语法。

6.2 重载方式

成员函数重载:

1
2
3
4
5
6
7
class A {
public:
A( int x ) { this->x = x; }
A operator+ ( const A & a ) const { return A( x + a.x ); }
private:
int x;
};

顶层函数重载:

1
A operator+ ( const A & a, const B & b ) { return A( a.x + b.x ); }

6.3 顶层函数进行操作符重载

  • 除了内存管理操作符new、new[]、delete、delete[]之外,一个以顶层函数形式重载的操作符必须在它的参数表中包含一个类对象。
  • 使用顶层函数进行重载的一个优点就是非对象操作数可以出现在操作符的左边。而使用类成员函数时,第一个操作数必须是类的对象。

6.4 输入与输出操作符的重载

  • 不能用顶层函数重载’=’
  • 只能将>>重载函数设计为顶层函数。
  • 重载时的流对象应该是引用形式(屏幕、键盘不能复制的吧)。重载>>时,应将参数设为引用,因为要改变这个数的值的。

6.5 前置后置运算符的重载

  • operator++(int):后置
  • operator++:前置

6.6 重载转型操作符

声明中不能包含返回类型,即使void也不行。如下所示:

1
operator othertype(); // othertype 可以为一个基本类型如int,或者一个类

6.7 内存管理操作符

  • 重载new、new[],顶层函数或者成员函数重载方式均返回void *类型。并且两种重载方式的第一个参数必须是size_t类型的参数。
  • 重载delete、delete[],两种重载方式均返回void,并且第一个参数必须是void *类型,用来指向需要释放的空间。