CPPv2

将函数放入结构体是从C到C++的根本改变。

在C中,结构体只是将一组相关的数据捆绑了起来,它除了是程序逻辑更加清晰之外,对解决问题没有任何帮助。

将处理这组数据的函数也加入到结构体中,结构体就有了全新的功能。它既能描述属性,也能描述对属性的操作。事实上,它就成为了和内置类型一样的一种全新的数据类型。

为了表示这是一种全新的概念,C++用了一个新的名称 — 类来表示。

面向对象程序设计的一个重要的目标是代码重用。

代码重用的两种方法:组合和继承。

组合是将某一个已定义类的对象作为当前类的数据成员,则对此数据成员操作的代码得到了重用。

继承是在已有类(基类)的基础上加以扩展,形成一个新类,称为派生类。在派生类定义时,只需要实现扩展功能,而基类有的功能得到了重用。

面向对象方法是用类的层次结构来体现类之间的继承和发展。而面向过程则强调过程的抽象化与模块化。

对于封装,可以使代码模块化。

对于继承,可以扩展已经存在的代码。

而多态,是为了接口重用。同样的功能不要再增加杂七杂八的名字。

从本质上讲,面向对象编程就是将数据和操作数据的过程视为一个对象:一个有身份和特征的独立实体。

创建对象,即分配了内存空间,并用对象名作为全部成员数据的首地址。

对象名就是首地址,按成员数据名称就可以偏移到相应的内存单元,按其类型解析出数据。

同样,成员函数是过程的封装,是算法的实现所在。

如果没有继承,类只有两种用户:类本身的成员和该类的用户。

有了继承,就有了类的第三种用户:从类派生定义新类的程序员。

public成员函数是类与外部程序的接口。

以下三条语句的作用是相同的:

void Point::setx(double inputx)// 静态成员函数不隐含*this
{
    x=inputx;
    this->x = inputx;
    (*this).x = inputx;
}

重载函数是通过“名字压延”方法来实现。即在编译时将函数名和参数结合起来创造一个新的函数名,用新的名字替换原有名字。(C的名字压延只是加了一个下划线)

继承一般有三种形式

1 实现继承:派生类使用基类的属性和方法而无需额外编码;

2 可视继承:子窗体使用父窗体的外观和实现代码;

3 接口继承:仅使用属性和方法,实现滞后到子类实现;

继承的过程是从一般到特殊的过程。

根据对象行为提取的成员函数应该设为公有的成员函数。公有函数的实现时分解出一些小函数通常被设计为私有的成员函数。

定义一个成员函数需要考量:

1 运算符函数还是非运算符函数?
2 自由运算符还是成员运算符?
3 虚函数还是非虚函数?
4 纯虚成员函数还是非纯虚成员函数?
5 静态成员函数还是非静态成员函数?
6 常量成员函数还是非常量成员函数;
7 公共的还是受保护的;
8 通过值、引用还是指针返回?
9 返回常量还是非常量?
10 参数是可选的还是必需的?
11 通过值、引用还是指针传递参数?
12 将参数作为常量传递还是非常量传递?
13 友元函数还是非友元函数?
14 内联函数还是非内联函数?

空类的长度是1byte,在实例化后可以有一个地址对应。

构造函数的初始化列表可以将数据成员的构造和赋初值一起完成,提高对象构造的时间性能。除此之外,还有两种情况必须用初始化列表。第一种情况是数据成员中含有一些不能用赋值操作进行赋值的数据成员,例如常量数据成员或对象数据成员,这时必须在初始化列表中调用数据成员所属类型的构造函数来构造它们。第二种情况是在用派生的方法定义一个类时,派生类对象中的基类部分必须在构造函数的初始化列表中调用基类的构造函数完成。

Examples of actions you might want to take in a destructor include releasing file handles, flushing network sockets, and freeing dynamic objects.

析构函数一般做的事情:

1 释放堆内存;
2 关掉一个文件;
3 关掉一个窗口;
4 释放一个lock;
5 对象计数器自减,如shared_ptr。

动态多态性发生在程序运行期,是动态绑定。动态多态则是通过继承、虚函数、指针来实现。程序在运行时才决定调用的函数,通过父类指针调用子类的函数,可以让父类指针具有多种形态。

多态的关键在于编译器行为:建立虚函数表、为对象建立虚函数指针;

动态多态:纵向多态;函数重载和模板:横向多态;

Polymorphism is A single name can have multiple meanings depending on its context.

所谓多态,简单地讲,就是指一个名字(或符号)具有多种含义。

为什么需要函数重载?

1 用相同的函数名来定义一组功能相同或类似的函数,程序的可读性增强;

2 这样做减少了函数名的数量,避免了名字空间的污染,而且减少对用户的复杂性;

3 简化代码,有效提高代码的可重用性,且体现了面向对象编程的优越性;

4 构造函数都与类名相同,如果没有函数重载,要想实例化不同对象将十分困难;

5 操作符重载,本质上也是函数重载,极大丰富了已有操作符的含义,方便使用。

一般情况下,重载输入(输出)运算符函数不能是类的成员函数。因为如果一个运算符函数是类的成员,则其左运算数就应当是调用运算符函数的类的对象,而此点是无法改变的。

重载输入(输出)运算符时,其左边的参数是流,而右边参数是类的对象。因此重载输入(输出)运算符函数必须是非成员函数-而用友元函数。

运算符函数重载:将指定的运算表达式转化为对运算符函数的调用,运算对象转化为运算符函数的实参。

友元可以是友元类,可以是友元函数,也就是独立于类的全局函数,如果该全局函数被某个类声明为友元,该全局函数就被授予了访问该类私有成员和保护成员的资格。

在函数模板中,如果模板的形式参数出现在函数形式参数表中,那么当函数调用时,编译器可以根据函数的实际参数的类型确定模板的实际参数,然后对函数模板进行实例化。在定义类模板的对象时,无法确定模板形式参数对应的实际参数值,因此只能在程序中显式地指出模板实际参数的值。

函数模板在调用时中,会有赋值操作,而类模板对象时,没有涉及到这样的赋值操作,所以要显示指定类型,就像基本数据类型的定义或类实例化一样,前面要有类型信息。

函数模板的类型可以通过函数调用进行推断。

字符串的字面量会保存到内存的数据段,字符串以外的字面量会以代码的形式存在。数组初始化不一样,其右值的字符串保存在栈中。

string really is a typedef for a template specialization basic_string and that they omit an optional argument relating to memory management. The type size_type is an implementation-dependent integral type defined in the string header file.The class defines string::npos as the maximum possible length of the string.Typically, this would equal the maximum value of an unsigned int.Also the table uses the common abbreviation NBTS for null-byte-terminated string—that is, the traditional C string, which is terminated with a null character.

引用本质上应该是一个常量指针,它的值初始化为其指向的变量,此后使用时无须解引用即直接表示其指向变量的值。它是一个常量,不能再指向其它变量,但指向的变量当然是一个可以更新的变量。引用做函数参数时,在原型中相当于指针,在函数体和返回时的使用类似变量。

返回引用的函数的主要用途

将函数用于赋值运算符的左边,即作为左值。

int a[] = {1, 3, 5, 7, 9};
int &index(int);  // 声明返回引用的函数
void main()
{
    index(2) = 25; // 将a[2]重新赋值为25
    cout << index(2);
}
int &index(int j)
{return a[j];}     // 函数是a[j]的一个引用

指针是一个特殊的变量,有自己的内涵,也有指它的内涵。

指针用做参数,通常是为了更新其所指的内容,如果想更新其自身,就需要双重指针或指针引用。

采用引用传递方式来传递一个指针非常有用。尤其当函数需要改变指针所存储的内存地址时,换句话说,当需要被传递的指针变量进行重定向时可以采用引用方式来传递它。 下面来看一个例子。

void first_bigger(int*& p, int threshold) 
{ 
    while (*p <=* threshold) 
        p++;
}

使用引用时还需要注意的是,首先,不能建立引用的数组。因为数组是某个数 据类型元素的集合,数组名表示该元素集合空间的起始地址,它自己不是一个名副其实的数 据类型。其次,引用本身不是一种数据类型,定义引用在概念上不产生内存空间,因此没有 引用的引用,也没有引用的指针。

try语句括起一个测试块,其中含有可能出错的语句(或其调用的函数体内)。如果有异常发生,则会抛出(throw)特定数据类型的异常。

每一个catch语句是一个异常处理器,相当于一个带参数的函数,功能是对指定异常类型进行处理。

一旦一个异常信号被抛出,那么与这个信号有相同类型参数的catch子句会捕获这个异常并处理。处理完成后整个异常处理流程结束。

&&的意思是“右值引用”,是一个我们可以绑定右值的引用。“rvalue”这个词是对“lvalue”的补充,它大致意思是“可以出现在赋值左侧的某个值”。因此,rvalue是——对于第一个近似值——一个不能赋值的值,例如函数调用返回的整数。因此,右值引用是对其他人无法分配的对象的引用,因此我们可以安全地“窃取”它的值。

随着程序和使用的类型变得越来越复杂,我们将看到静态类型检查能帮助我们更早地发现错误(最开始,一些编程语言并没有类型的概念,都等长处理)。静态类型检查使得编译器必须能识别程序中的每个实体的类型。因此,程序中使用变量前必须先定义变量的类型。

当需要将一个较大的算术类型赋值给较小的类型时,使用强制转换非常有用。此时,强制类型转换告诉程序的读者和编译器:我们知道并且不关心潜在的精度损失。对于从一个较大的算术类型到一个较小类型的赋值,编译器通常会产生警告。当我们显式地提供强制类型转换时,警告信息就会被关闭。

如果编译器不提供自动转换,使用 static_cast 来执行类型转换也是很有用的。例如,下面的程序使用 static_cast 找回存放在 void* 指针中的值:

    double d = 3.14;
    void* p = &d;
    double *dp = static_cast<double*>(p);

强制类型转换关闭或挂起了正常的类型检查。强烈建议程序员避免使用强制类型转换,不依赖强制类型转换也能写出很好的 C++ 程序。

C++中的reinterpret_cast主要是将数据从一种类型的转换为另一种类型。所谓“通常为操作数的位模式提供较低层的重新解释”也就是说将数据以二进制存在形式的重新解释。比如:

int i;
char *p = "This is an example.";
i = reinterpret_cast<int>(p);  // 4648988

此时结果,i与p的值是完全相同的。reinterpret_cast的作用是说将指针p的值以二进制(位模式)的方式被解释为整型,并赋给i,//i 为整型;一个明显的现象是在转换前后没有数位损失。

temporary object临时对象

Unnamed object automatically created by the compiler in the course of evaluating an expression. The phrase temporary object is usually abreviated as temporary. A temporary persists until the end of the largest expression that encloses the expression for which it was created.

在求解表达式的过程中由编译器自动创建的没有名字的对象。“临时对象”这个术语通常简称为“临时”。临时对象一直存在直到最大表达式结束为止,最大表达式指的是包含创建该临时对象的表达式的最大范围内的表达式。

Associative containers differ in a fundamental respect from the sequential containers: Elements in an associative container are stored and retrieved by a key, in contrast to elements in a sequential container, which are stored and accessed sequentially by their position within the container.

关联容器和顺序容器的本质差别在于:关联容器通过键(key)存储和读取元素,而顺序容器则通过元素在容器中的位置顺序存储和访问元素。

There is one important consequence of the fact that elements are ordered by key: When we iterate across an associative container, we are guaranteed that the elements are accessed in key order, irrespective of the order in which the elements were placed in the container.“容器元素根据键的次序排列”这一事实就是一个重要的结论:在迭代遍历关联容器时,我们可确保按键的顺序的访问元素,而与元素在容器中的存放位置完全无关。

应该使用指针的情况: 可能存在不指向任何对象的可能性,需要在不同的时刻指向不同的对象(此时,你能够改变指针的指向) 。返回函数体中new出的内存空间的地址。多态中使用指针。

应该使用引用的情况: 如果总是指向一个对象并且一旦指向一个对象后就不会改变指向,使用此时应使用引用。

C++大胆地运用了类型修正符(type modifier),对原型进行一些修改和限制。主要包括:const、volatile、mutable

const用于规定变量的不可修改性;

mutable用于成员变量,指当声明一个const对象时,如果某个成员是mutable属性的,则该成员变量可以被修改,但是整体的“常量性”不变。

volatile是指当一个变量被优化存储到寄存器中时,在不断从寄存器中调用时,可能在内存中有对其修改,但没有同步更新的寄存器,volatile是告诉编译器,不能优化存储到寄存器中,以免出现更新不同步的错误。

C++从C语言继承过来,但是我们的Bjarne博士更具有先见之明,他为了避免受到C语言的局限性,参考了很多的语言,例如:从Simula继承了类的概念,从Algol68继承了运算符重载、引用以及在任何地方声明变量的能力,从BCPL获得了//注释,从Ada得到了模板、名字空间,从Ada、Clu和ML取来了异常。

强类型的C++的泛型,除了使用模板,函数重载以外,还有类型方面的泛化,如void、variant、union。

存储类说明符 类型限定符 类型修饰符 标识符
specifier delimiter modifier identifier
extern volatile unsigned long int i;
volatile unsigned long int i=0;
存储类说明符:内存哪个位置?如static,register
modifier:类型修正:对原型进行一些修改或限制:const、volatile,C++
类型限定:long、unsigned

代码编辑器采用何种编码方式决定了字符串最初的编码,比如编辑器如果采用GBK,那么代码文件中的所有字符都是以GBK编码存储。当编译器处理字符串时,可以通过前缀来判断字符串的编码类型,如果目标编码与原编码不同,则编译器会进行转换,比如C++11中的前缀u8表示目标编码为UTF-8的字符,如果代码文件采用的是GBK,编译器按照UTF-8去解析字符串常量,则可能会出现错误。

机器字长:CPU 一次能处理数据的位数,与 CPU 中的 寄存器位数有关

一个字符集就是在字符与整数值之间的一种映射;

basic_string是一个模板类,string是模板形参为char的basic_string模板类的类型定义;

'basic_string, allocator > string;

C语言的类型转换问题在于操作比较含糊,而C++的类型转换更容易更醒目;

每个普通的方法调用都会传递一个指向对象的指针,这就是称为“ 隐藏”参数的this指针。使用这个指针可访问数据成员或者调用方法,也可将其传递给其他方法或函数。有时还用它来消除名称的歧义。

委托构造函数delegating constructors允许构造函数在初始化列表中调用同一个类的其他构造函数。

对象的移动语义move semantics需要实现移动构造函数move constructor和移动赋值函数运算符move assignment operator。如果源对象是操作结束后被销毁的临时对象,编译器就会使用这两个方法。这两个方法将数据成员从源对象移动到新对象,然后使源对象处于有效但不确定的状态。通常会将源对象的数据成员重置为空值。这样做实际上将内存和其他资源的所有权从一个对象移动到另一个对象。这两个方法基本上只对成员变量进行表层复制(shallow copy),然后转换已分配内存和其他资源的所有权,从而阻止悬挂指针和内存泄漏。

编译器必须在编译期间对constexpr函数求值,函数也不允许有任何副作用。

函数不可以嵌套定义,但可以嵌套lambda表达式;

临时对象

int a=3;
int b=4;
int c=a+b;

CPU计算a+b,即不能借用a和b的内存,也不能借用赋值目标c的内存,总得有个临时存储位置吧?这位置可能是某块临时内存,也可能是某些个寄存器。

动作数据化的三种方法:

1 函数指针;
2 函数对象;
3 Lambda函数;