PHP面试题(二)

前言

从网上找了一套号称是百度的php面试题目,这里记录一下

PHP的gc机制

php的垃圾回收机制注意以下几点即可:
  • 引用计数refcount和is_ref,也就是php不会随意的malloc内存空间,而是用类似c的指针的方式,增加引用计数,引用计数为0就free掉变量,每个变量在底层实现都是一个在zval的结构体
  • php5.3之前无法解决循环引用计数的问题,会导致内存泄漏.php5.3以后,采用深度优先遍历解决了这个问题,具体实现细节我也不清楚

PHP实现单链表

<?php

/**
 * 用class模拟struct,实现链表节点定义
 */
class Node
{

    /**
     * 数据
     */
    public $data;

    /**
     * 下一个节点
     */
    public $next;

    public function __construct ($data, $next = null)
    {
        $this->data = $data;
        $this->next = $next;
    }
}

class LinkList
{

    public $head;

    public function __construct ()
    {
        $this->head = null;
    }

    /**
     * 尾插法实现链表插入操作
     *
     * @param int $value            
     */
    public function insertNode ($value)
    {
        $cur = $this->head;
        $pre = null;
        
        while ($cur != null) {
            $pre = $cur;
            $cur = $cur->next;
        }
        
        $new = new Node($value);
        $new->next = null;
        
        if ($pre == null) {
            $this->head = $new;
        } else {
            $pre->next = $new;
        }
    }

    /**
     * 单链表中删除指定节点
     *
     * @param int $value            
     */
    public function deleteNode ($value)
    {
        $cur = $this->head;
        $pre = null;
        
        while ($cur != null) {
            if ($cur->data == $value) {
                if ($pre == null) {
                    $this->head->next = $cur->next;
                } else {
                    $pre->next = $cur->next;
                }
                break;
            }
            $pre = $cur;
            $cur = $cur->next;
        }
    }

    /**
     * 打印单链表
     */
    public function printList ()
    {
        $cur = $this->head;
        while ($cur->next != null) {
            printf("%d ", $cur->data);
            $cur = $cur->next;
        }
        printf("%d\n", $cur->data);
    }
}

// 测试
$list = new LinkList();
$list->insertNode(1);
$list->insertNode(2);
$list->insertNode(3);
$list->insertNode(4);
$list->insertNode(5);
$list->insertNode(6);

$list->printList();

$list->deleteNode(4);
$list->printList();

PHP实现字符串反转

<?php

function strReverse(&$str)
{
    for ($i = 0, $j = strlen($str); $i <= $j; $i ++, $j --) {
        $tmp = $str[$i];
        $str[$i] = $str[$j];
        $str[$j] = $tmp;
    }
}

$str = "wangzhengyi";
strReverse($str);
echo $str;

PHP变量的内部实现

编程语言的系统类型分为强类型和弱类型两种:
  • 强类型语言是一旦某个变量被申明为某个类型的变量,在程序运行过程中,就不能将该变量的类型以外的值赋予给它,c/c++/java等语言就属于这类
  • php及ruby,javascript等脚本语言就属于弱类型语言:一个变量可以表示任意的数据类型

php变量类型及存储结构

php在声明或使用变量的时候,并不需要显式指明其数据类型

php是弱类型语言,这不并表示php没有类型,在php中,存在8种变量类型,可以分为三类:
  • 标量类型:boolean,integer,float,string
  • 复合类型:array,object
  • 特殊类型:resource,NULL

变量存储结构
变量的值存储到一下所示的zval结构体中.其结构如下:

typedef struct _zval_struct zval;

struct _zval_struct {
	zvalue_value value;	// 存储变量的值
	zend_uint refcount__gc;	// 表示引用计数
	zend_uchar type;	// 变量具体的类型
	zend_uchar is_ref_gc;	// 表示是否为引用
};

变量的值存储在另外一个结构体zvalue_value中

变量类型
zval结构体的type字段就是实现弱类型最关键的字段了,type的值可以为:IS_NULL, IS_BOOL, IS_LONG, IS_DOUBLE, IS_STRING, IS_ARRAY, IS_OBJECT, IS_RESOURCE之一.从字面上就很好理解,他们只是类型的唯一标示,根据类型的不同将不同的值存储到value字段

变量值的存储

前面说到变量的值存储在zvalue_value结构体中,结构体定义如下:

typedef union _zvalue_value {
	long lval;
	double dval;
	struct {
		char *val;
		int len;
	} str;
	HashTable *ht;
	zend_object_value obj;
} _zvalue_value;

数组Array
数组是PHP中最常用的,也是最强大的变量类型.它可以存储其他类型的数据,而且提供各种内置操作函数.数组的存储相对于其他变量要复杂一些,数组的值存储在zvalue_value.ht字段中,它是一个HashTable类型的数据.PHP的数组使用哈希表来存储关联数据,哈希表是一种高效的键值存储结构.PHP的哈希表实现中使用了两个数据结构HashTable和Bucket.

哈希表(HashTable)

哈希表是一种通过哈希函数,将特性的键映射到特定值的一种数据结构,它维护键和值之间一一对应的关系.
  • 键(key):用于操作数据的标示,例如PHP数组中的索引
  • 槽(slot/bucket):哈希表中用于表村数据的一个单元,也就是数据真正存放的容器
  • 哈希函数(hash function):将key映射到数据应该存放的bucket所在位置的函数
  • 哈希冲突(hash collision):哈系函数将两个不同的key映射到同一个索引的情况

哈希表的实现
在了解到哈希表的原理之后要实现一个哈希表也很容易,主要需要完成的工作只有三点:
  1. 实现哈希函数
  2. 冲突的解决
  3. 操作接口的实现

数据结构
首先我们需要一个容器来保存我们的哈希表,哈希表需要保存的内容主要是保存进来的数据,同时为了方便得知哈希表存储的元素个数,需要保存一个大小字段,第二个就是需要保存数据的容器了.基本的数据结构有两个,一个用户保存哈希表本身,一个用户实际保存数据的单链表,定义如下:

typedef struct _Bucket {
	char *key;
	void *value;
	struct _Bucket *next;
} Bucket;

typedef struct _HashTable {
	int size;	// 哈系表大小
	int elem_num;	// 已经保存的元素个数
	Bucket **buckets;

} HashTable;

为了简化,这里key的数据类型为字符串,而存储的数据类型可以为任意类型
Bucket结构体是一个单链表,这是为了解决多个key哈希冲突的问题,也就是前面所提到的链接法.当多个key映射到同一个index的时候将冲突的元素链接起来

哈系函数的实现
哈系函数需要尽可能的将不同的key映射到不同的槽(bucket)中,首先我们采用一种最为简单的哈希算法实现:将key字符串的所有字符加起来,然后以结果对哈系表的大小取模,这样索引就能落在数组索引的范围之内了

static int hash_str(char *key)
{
	int hash = 0;
	char *cur = key;

	while (*(cur ++) != '\0') {
		hash += *(cur - 1);
	}
	return hash;
}

// 使用这个宏来求得key在哈希表中的索引
#define HASH_INDEX(ht, key) (hash_str(key) % (ht)->size)

操作接口的实现
为了操作哈希表,实现了如下几个接口:

// 接口函数
int hash_init(HashTable *ht);
int hash_lookup(HashTable *ht, char *key, void **result);
int hash_insert(HashTable *ht, char *key, void *value);
int hash_remove(HashTable *ht, char *key);
int hash_destroy(HashTable *ht);

初始化函数

/**
 * 初始化hash表
 */
int hash_init(HashTable *ht)
{
	ht->size = HASH_TABLE_INIT_SIZE;
	ht->elem_num = 0;
	ht->buckets = (Bucket **)calloc(ht->size, sizeof(Bucket *));

	if (ht->buckets == NULL)	return FAILED;
	
	return SUCCESS;
}

初始化主要工作是为哈希表申请存储空间,函数中使用calloc函数的目的是确保数据存储的槽都初始化为0,以便后续在插入和查找时确认该槽是否被占用


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值