c++编程之模板和泛型编程

我们对传递数值或变量给函数都很熟悉,除了传递变量,我们还能传递类型给模板。传递类型就是大家所熟知的泛型编程,因为 我们可以用泛型编写程序,而用特定的类型调用。

泛型编程的目的是为了编写的程序不依赖于数据类型。在 C 语言中,所有的代码都需要绑定到确定的数据类型,这样写的代码只能对特定的数据类型起作用。 而模板可以让我们实现泛型编程。你可以将类型作为参数来构建模板函数和类模板。当你的算法需要作用于多种数据类型的时候,模板就显得及其有用了。

C++的标准模板库(STL)提供了一些常用的容器类模板的实现,例如vector,可以用来存放所有类型的元素。

C/C++中的内置数组有一些缺点:

  1. 它的大小是固定的,需要在声明的时候确定大小,不支持动态声明。你不能在执行期给数组扩容;
  2. 数组不提供下标边界校验,你可以使用超出边界的下标
  3. 你需要自己实现数组比较,和赋值操作

C++提供了一个vector类模板,作为标准模板库(STL)的一部分。vector被定义在<vector>头文件中,属于std命名空间。vector 是最常用的 STL 类,它能够取代数组,并且支持动态分配空间和一些其它操作(例如比较和赋值)。

vector 是一个类模板,它可以被特定类型的实例化,形如:vector<int>, vector<double>, vector<string>。同一个模板能够用于多种类型,而不必为每种类型都写一套实现。

#include <iostream>
#include <vector>
#include <string>

using namespace std;

void print(const vector<int> &v);

int main(int argc, char *argv[]) {
	vector<int> v1(5); // Create a vector with 5 elements.

	// Assign values into v1, using array-like index []
	// You can retrieve the size of vector via size()
	for (int i = 0; i < v1.size(); i++) {
		v1[i] = (i + 1) * 2;
	}

	// Print vector content, using at()
	for (int i = 0; i < v1.size(); i++) {
		cout << v1.at(i) << " ";
	}
	cout << endl;

	vector<int> v2;
	// Assign v1 to v2 memberwise
	v2 = v1;
	for (int i = 0; i < v2.size(); i++) {
		cout << v2[i] << " ";
	}
	cout << endl;

	// Compare 2 vectors memberwise
	cout << boolalpha << (v1 == v2) << endl;

	// Append more elements - synamically allocate memory
	v1.push_back(80);
	v1.push_back(81);
	for (int i = 0; i < v1.size(); i++) {
		cout << v1[i] << " ";
	}
	cout << endl;

	vector<string> v3;
	v3.push_back("a for apple");
	v3.push_back("b for boy");
	for (int i = 0; i < v3.size(); i++) {
		cout << v3[i] << " ";
	}

	cout << endl;

	return 0;
}

说明:

c++编程之操作符重载

操作符重载就是指操作符会对不同类型的操作数表现出不同的行为。例如:(a) 按位左移操作符’«‘在操作流对象的时候就变成了插入操作;(b) *操作符操作于两个数字的时候就是乘法操作,而作用于 地址的时候就是间接寻址操作。C++允许你再用户端扩展操作符重载。

操作符重载就像函数重载那样,同一个函数名可以因为参数不同而同时存在很多版本。

C++的"string"类中重载了以下操作符来作用于"string"对象:

  • 字符串比较操作(=,!=,>,<,>=,<=):例如,使用str1 == str2来比较两个"string"对象
  • 流插入和取出操作(«,»):例如,你可以使用cout << st1cin >> str2来输出/输入"string"对象
  • 字符串连接(+,+=):例如,str1 + str2,将两个"string"对象合并成一个新的"string"对象,str1 += str2str2追加到str1
  • 字符索引或下标([]):例如,你可以使用str[n]来获取下标为 n 的字符;或者str[n] = c来修改下标为 n 处的字符。需要注意的是’[]‘操作符不会 做边界检测,也就是说,你需要自己保证下标不会超出边界,你可以使用"string"类的at()函数来做边界检测
  • 赋值(=):例如,str1 = str2,将str2赋值给str1

示例代码:

#include <iostream>
#include <iomanip>
#include <string>

using namespace std;

int main(int argc, char *argv[]) {
	string msg1("hello");
	string msg2("HELLO");
	string msg3("hello");

	cout << boolalpha;
	cout << (msg1 == msg2) << endl;
	cout << (msg1 == msg3) << endl;
	cout << (msg1 < msg2) << endl;

	string msg4 = msg1;
	cout << msg4 << endl;

	cout << (msg1 + " " + msg2) << endl;
	msg3 += msg2;

	cout << msg3 << endl;

	cout << msg1[1] << endl;
	cout << msg1[99] << endl;

	return 0;
}

为了实现操作符重载,我们需要使用一种特殊的函数形式,叫做操作符函数。操作符函数形如:“operator Δ()",Δ 就是将要被重载的操作符。

c++编程之继承和多态

超类(基类)和子类(派生类):在面向对象程序设计中,我们通常使用继承来避免代码冗余。在 C++中,继承的语法规则如下:

class SubclassName : inheritance-access-specifier SuperclassName {
	......
};

子类继承了父类所有的成员,子类也可以定义自己的构造器和成员。

c++编程之OOP示例

回到之前版本的"Time"类,假设我们想增加链式操作,例如t.nextSecond().nextSecond().print(),我们就需要让nextSecond()返回this的一个引用。

Time.h

class Time {
private:
	......
public:
	Time &nextSecond(); // Return a reference to "this" instance
	......
}

在函数原型中,我们申明了一个nextSecond()的成员函数,返回Time对象的引用。返回的引用可以用来继续调用成员函数。

c++编程之指针,引用和内存动态分配

指针,引用和动态分配内存是 C/C++语言中最强大的特性,这些特性使得程序员能够直接操作计算机中非常珍贵的记忆体资源,进而对内存进行最大性能和高效的使用。 然而指针也是一把双刃剑,它也是 C/C++编程语言中最复杂和最难的特性。

指针之所以这么强大,是因为它允许你通过地址来访问和操作对应记忆体中存储的内容。但是指针也很难被驾驭,使用好的话确实能够大大提升性能,而用的 不好的话,也会导致很多问题,例如著名的内存泄漏和缓冲区溢出,这些 bug 会致使系统发生紊乱。一些新的编程语言(例如 Java 和 C#),将指针从它的语法 中去掉,通过提供自动内存管理的方式来避免使用指针导致的许多问题。

虽然你在编写 C/C++代码的时候可以不使用指针,但是在学习 C/C++的时候很难不提及指针。指针也许不是为新手和笨蛋而设计的。

计算机记忆体位置有一个地址,对应地址处保存数据。记忆体地址通常是一个数字(一般用十六进制表示),这个数字很难被程序员直接使用。通常一个地址位置 的容量是 8-bit(也就是 1-byte),里面可以存储整数,实数,字符或者字符串,这完全取决于程序员如何解析。

为了减轻程序员使用数字地址和解析数据的负担,早期的编程语言(例如 C 语言)中产生了一种新的变量——这种变量是一个被命名了的变量的位置,它可以存储一个特定类型的值。 取代数字地址的是用名字(或者标识符)直接关联到确定的地址上,而且变量类型(如int, double, char)与之关联,从而简化了对数据的解析。

每个内存地址占 8 位(也就是 1 个字节),一个 4 个字节的int值需要 4 个内存位置。一个 32 位的系统通常使用 32 位的地址。同样的,存储这个 32 位的地址也需要 4 个内存位置。

下面的插图形象的描绘了计算机内存地址,内存中数据,变量名,变量类型以及变量值之间的关系。

内存地址、数据与变量的关系

指针变量(简称指针)基本上跟其他变量一样,都可以用来存放数据,但是跟普通变量不同的是,普通变量存储的是数值,而指针存放的是内存地址。

指针在使用前必须先申明。申明指针的语法是在指针名前加上一个*符号。指针必须跟类型关联。

type *ptr;
// or
type* ptr;
// or
type * ptr;

例如:

c++编程之面向对象

假如你想组装一台电脑,你会去硬件商店购买主板、处理器、内存条、硬盘、机箱、电源,然后将它们组装在一起,然后打开电源,电脑就能运行。 你不用考虑主板是 4 重板还是 6 重板,硬盘是什么尺寸,内存是哪里生产的诸如此类的问题。你只需要将这些硬件单元组合在一起,就能期待电脑能够运行。 当然,你需要保证你有正确的接口,比如,如果你的主板只支持 IDE 接口,而你需要购买一个 IDE 的硬盘而不是 SCSI 硬盘,又例如你需要选择一个合适速率的 内存。即便如此,将硬件组件组装成一台机器也没有任何难度。

同样的,一辆车也是由多个部分组装起来的,例如底盘、车门、引擎、车轮、刹车和传动装置。这些组件都是可复用的。比如车轮,就能够被用于很多辆同型号的骑车上。

像电脑和汽车这样的硬件能够用可复用的部分组装起来。那么软件是否也是如此呢?我们是否能够将不同地方的程序片段“组装”起来,然后期待程序能正常运行呢? 答案显然是 no!跟硬件不同的是,很难从软件片段中“组装”一个应用。自从计算机 60 年前问世以来,人们写了大量的程式码。然而,对于每一个新的应用程序,我们都需要 重新造轮子。

为什么要重新造轮子呢?

面向过程编程中的函数组织方式

传统的面向过程的编程语言(例如 C 和 Pascal)在创建可重用组件方面遇到了一些显著的缺陷:

  1. 程序是由函数组织起来的。函数通常是不可重用的,我们很难将一个函数直接拷贝到别的地方去使用,因为函数很有可能引用了头文件,或者全局变量,或者调用了其他函数。 换句话说,函数不能很好的封装成一个独立的可重用单元。

  2. 面向过程的语言不适合高层抽象来解决现实生活中的问题。例如 c 程序使用"if-else", “for-loop”, “array”, “function”, “pointer"等结构,这些结构很低阶而且很难抽象形如 Customer Relationship Management (CRM) 系统或者电脑足球游戏。

简而言之,传统的面向过程的编程语言将数据结构和算法单元分开了。

面向对象编程中的对象组织方式

面向对象的编程语言就是被设计来克服这些问题的。

  1. OOP 的基础单元是类。类将静态属性和动态行为封装在一起,同时指定一些公开的接口来供人使用。由于相比于函数,类有很好的封装性,所以很容易重用。换句话说,类将数据和算法结合在了一起。

  2. 面向对象的编程语言为解决现实问题的高阶抽象提供了保证。面向过程的编程语言迫使人们把注意力放在计算机结构(如:内存,位,字节,数组)上,而不是放在要解决的问题本身。面向对象的编程语言 能够让我们更专注于问题本身,使用程序对象来表示和抽象问题中的各种实体。

举个例子,假设你要写一个足球游戏,很难用面向过程的语言建立模型。但是使用 OOP,可以很容易将现实事物同程序之间建立模型:

  • Player:属性包含 name, number, location 等待,操作有 run, jump, kick-the-ball…
  • Ball:
  • Reference:
  • Field:
  • Audience:
  • Weather:

最重要的是,这其中的一些类(例如:Ball 和 Audience)可以在其他程序中复用。

PHP7虚拟机

原文地址http://nikic.github.io/2017/04/14/PHP-7-Virtual-machine.html

写这篇文章的目的是基于 php7,阐述 Zend Virtual Machine 的内部实现。这不是一篇综合描述,我将尽可能地覆盖到所有重要的部分和细节。

本文的描述对象是 php7.2 版本,但是几乎所有的特性都已经应用在了 php7.0/7.1 中了。然而,它们同 php5.x 系列 VM 的不同之处同样也很重要,我会很有耐心的同步描述。

这篇文章主要是从指令的角度来阐述,只有在末尾花了少量篇幅描述了 C 语言实现 VM 的细节。但是在文章开始之前,我想先提供一些实现 VM 的主要代码文件:

首先我们来聊聊 opcode。“Opcode"是用来表示整个 VM 指令集(包括操作数)的,但是也可能仅仅就是指“真实的”操作码,这些操作码是一个很小的整数用来区分不同的指令类型。其具体的含义需要结合代码的上下文才能清楚。在程式码中,指令通常被称作"oplines”。

下面是zend_op的结构

struct _zend_op {
    const void *handler;
    znode_op op1;
    znode_op op2;
    znode_op result;
    uint32_t extended_value;
    uint32_t lineno;
    zend_uchar opcode;
    zend_uchar op1_type;
    zend_uchar op2_type;
    zend_uchar result_type;
};

由此看来,opcodes 本质上就是一个“三地址码”格式的指令。有一个opcode代表指令的类型,有两个输入操作数op1op2和一个输出操作数result

PHP和线程

原文地址http://blog.jpauli.tech/2017/01/12/threads-and-php.html

PHP 和线程,单凭这简短的几个字,就足以写一本书。像往常一样,我们不会这么做,但是会给出一定程度上跟这个话题相关的信息与细节。让我们从一些人在谈论这个话题时通常感到的困惑开始,PHP 不是一种线程语言, PHP 的内核没有使用线程,而且 PHP 天生也不允许用户层代码通过任何方式使用多线程作为并发机制。

因此 PHP 跟其他一些技术有很大的区别,例如 Java。Java 不仅自身使用了大量的线程,它还允许用户通过编程来是用线程。然而,PHP 不适用线程是有它的原因的。

PHP 内核没有使用线程,主要是为了简化开发。当你读到下一节的时候,你就会了解到线程并不是一个能使任何程序都能更快运行的魔法技术。是不是听起来很像是在推销不是吗?但是我们不是推销,而是谈论技术,而且 我们很清楚我们在说什么。因此 PHP 引擎目前没有使用多线程,也许将来会使用。但是使用多线程在编程上会引发很多问题,例如程序运行结果不是你所期待的等等。主要的困难是跨平坦的多线程编程,其次就是资源共享和 锁的管理,再次就是并不是所有的程序都能够被转化成多线程程序。PHP 的设计主要在 2000 前后,在那个时候,多线程编程并不是很广泛和成熟,PHP 引擎开发工程师决定创造一个完全没有线程的单片机引擎(当然他们也没有 足够的能力去驾驭一个稳定的跨平台的多线程引擎)。

PHP 用户层代码也不允许使用线程,因为那不是 PHP 期待你的代码运行的方式。PHP 是一个"发送并忘记(fire-and-forget)“型的语言,你应该尽可能快的处理完请求,然后释放,然后接着处理下一个请求。PHP 被设计作为一种 胶水语言:你不用处理可能使用到线程的复杂任务,而是访问快速而且已经准备好的资源,将它们粘合到一起,然后再返回给用户。通过 PHP,无论什么可能花费比通常时间多的时间的任务,都不能用 PHP 来处理。这就是为什么 在 PHP 中我们通常使用基于消息队列的系统(Gearman, AMQP, ActiveMQ 等等)来异步处理一些耗时任务。正如 Unix 看待事物的方式:“开发小而完备的工具,然后将他们连接在一起”。因此 PHP 的设计不是允许大规模的并行,而是 其他专门的技术–是用正确的工具来解决特定的问题。

让我们来快速的介绍下线程。注意,我们不会阐述太多细节的东西,对于你想深入了解关于线程的任何细节,都可以在相关书籍和站点上找到。

线程是进程中的轻量的事务处理单元,注意,一个进程可以产生多个线程,一个线程必须有且只能属于一个进程。进程是操作系统中的基本工作处理单元。在多 CPU 的机器上,不同的 CPU 将会平行工作,这样对于计算能力的提升会 有很大的好处。如果进程 A 和 B 都准备被执行,而且两个 CPU(或者两个 CPU 核心)也都有空闲的负载,那么进程 A 和 B 将会同时被执行。因此,计算机将能高效的在一个单位时间内同时进行多个运算,我们称之为“并行”(parallelism)。

进程

进程
进程的内存结构

理解c语言中的声明

在阅读优秀的 c 语言开源程式的时候,我们经常会看到各种复杂的声明,顿时会让我们怀疑人生,怀疑自己是否真的看得懂 c 语言。然而冷静三秒钟,透过现象看本质,发现牛人写的代码并不是“天书”, 也是很好懂的,关键是要冷静和耐心去阅读。

(*(void(*)())0)()

下面我们来一步步分析:

PHP扩展开发之迭代器

在之前的文章中,我们已经实现了一些 object handlers 来将我们的 ArrayBuffer 整合到 php 中。但是美中不足的是,我们的 ArrayBufferView 并不支持迭代器操作。也就是它不能像 php 中的数组那样使用foreach来遍历。 那么,我们接下来就来看看迭代器在内核中是如何实现的,并且给我们的 ArrayBufferView 也增加一个迭代器。

内核中的迭代器跟用户端的IteratorAggregate接口功能是一样的。一个具有迭代功能的类都有一个get_iterator处理器,它会返回一个zend_object_iterator *类型的结构,该结构定义如下(位于 phpsrc/Zend/zend_iterators.h 中):

struct _zend_object_iterator {
    void *data;
    zend_object_iterator_funcs *funcs;
    ulong index; /* private to fe_reset/fe_fetch opcodes */
};

其中的index成员就是内核中用以实现foreach的,它的值会在每次迭代后增加。funcs成员包含了不同的迭代操作: