C++手稿:STL中的函数对象与函数指针

C++ STL 指针 模板 运算符 函数对象 函数指针

STL是C++的标准模板库(standard template library),自然其中定义的都是模板。 相比于类和函数声明的显式接口(explicit interface),类模板和函数模板声明的接口属于隐式接口(implicit interface)。 因为模板参数应当满足的接口是由模板中表达式的合法性决定的,这一点给了模板很大的自由。 而函数对象函数指针具有同样的调用语法,因此STL中这两者常常可以互换。

更多关于隐式接口和显式接口的概念和区别,参见Effective C++: Item 41

先来感受一下C++中的函数对象和函数指针:

template<typename T>
void printer(int a, int b, T func){
    cout<<func(a, b)<<endl;
}

在STL中定义了很多像上面这样的模板,这里的T是一个可调用(实现了括号运算符)的东西。 这使得我们在使用模板时可以指定一个计算策略,它可以是函数对象,也可以是函数指针。

Less<int>便是一个常见的函数对象,常用来配置容器或算法。<functional>中定义了很多这样的函数对象。

函数指针

函数指针通常用来将函数传参或存储。例如:

int sum(int a, int b){
    return a+b;
}
int main(){
    printer(2, 3, sum);
    return 0;
}

上述的printer调用方式,编译器会生成对应的函数实例:

void printer(int a, int b, int (*func)(int, int)){
    cout<<func(a, b)<<endl;
}

这里T的类型是int (*)(int, int)

如果你是python或者javascript程序员的话,上述过程没有什么特别的。 唯一要注意的是func的声明方式,星号要和标识符括起来:(*func)

函数对象

函数对象是重载了括号运算符的类的实例,它也可以这样调用:func(a, b)。例如:

class Sum{
public:
    int operator()(int a, int b){
        return a+b;
    }
};

int main(){
    printer(2, 3, Sum());
    return 0;
}

编译器会生成这样的函数实例:

void printer(int a, int b, Sum s){
    cout<<s(a, b)<<endl;
}

函数对象可以实现更加复杂的有状态的运算,因为对象可以有更多的属性和方法。

C++手稿:STL小记

C++ STL 容器 排序 数组 模板 算法 链表 集合 二叉树 运算符 迭代器 平衡二叉树

STL中有三个重要的概念: 1. 容器:容纳各种数据类型的数据结构,是一系列的类模板。 2. 迭代器:迭代器用来迭代地访问容器中的元素。 3. 算法:用来操作容器中的元素,是一系列的函数模板。

C++手稿:std::string

C++ 字符串

字符串在很多编程语言中已经成为基本数据类型,C语言中我们使用char*来手动维护字符串的内存, 在C++中,可以使用std::string来方便地创建和操作字符串。

string是一个模板类,它有basic_string<T>定义:

typedef basic_string<char> string;

C++的string可以通过成员方法c_str()转换为C语言的char*

参考文档:cplusplus.com/string

初始化与赋值

string有两个常用的构造函数:

// 用一个C字符串构造
string str("hello");
// 等价于
string str = "hello";

也可以用N个同样的字符来构造字符串:string str2(8, 'x')

在C0x标准中,std::to_string可以将很多类型转换为一个string,可以代替itoa,例如:

string str = to_string(123);

string构造函数不接受charint类型。

字符串可以直接互相赋值,内存会自动拷贝和销毁,我们大可不必管它。对于单个字符赋值可以使用下标运算符:

for(int i=0;i<str.length(); i++){
    str[i] = 'a';
}

与多数class类似,string也提供了swapstr1.swap(s2)将会交换二者的值。

运算符支持

有通用运算符支持的数据类型往往更容易理解和操作,其中最讨人喜欢的莫过于+运算符:

str += str2;
str = str + "hello";

当然,你也可以直接调用append方法:str.append(str2)

除了+string还支持一系列的比较运算符:<, ==, >, <=, >=, !=

当然,你仍然可以直接调用compare方法:str1.compare(str2)str1小则会返回-1

C++手稿:封装与继承

C++ 封装 继承 作用域 多继承 名称隐藏 对象组合 构造函数 析构函数

本文总结了C++中类的继承相关的概念,包括可见性级别、继承的不同方式、构造与析构过程、封闭类、友元等。

可见性级别

C++类提供了数据结构和算法的封装,以及相应的3种可见级别。它们定义了不同的可见性:

  • Public:当前类以及子类的方法中可见,外部可见。
  • Protected:当前类以及子类的方法中可见,外部不可见。
  • Private:当前类的方法中可见,外部不可见。

在一个对象的成员函数中,可以调用其他同类对象的私有方法。

多数现代的面向对象语言中,仅提供Private和Public两种可见性,C++的可见级别略显复杂。 然而三种继承方式以及多继承机制,让问题更加复杂。简单起见,此处只讨论Private和Public方式的单继承。

  • Public继承:子类中可访问基类publicprotected成员,子类外部可见父类public成员。
  • Private继承:子类中可访问基类publicprotected成员,子类外部不可见父类成员。

类的继承

  • Public继承表示"is a"的关系(见Effective C++: Item 32),子类的对象同时也是一个基类的对象。 子类的行为应符合基类的行为,因此Public继承中通常不会覆盖基类成员。

  • Private继承表示“以…实现“的关系,子类是以基类来实现的 对于一个子类的对象,其外部不可见基类的行为。Private继承更像是对象组合。

对于Public继承,子类的指针、引用、变量可以直接赋值给基类的指针、引用、变量。

class CBase{};
class CDerived: public CBase{
public:
    CDerived(): CBase(){}
};

C++手稿:虚函数与多态

C++ 多态 引用 指针 继承 虚函数 成员函数 构造函数 析构函数

C++类继承带来了诸多好处:基类代码复用、通用的方法和属性、更好的可维护性, 然而最大的好处莫过于提供统一的接口。接口是一种对类型的抽象,它统一了一系列类的行为, 不同类的对象之间交互更加容易。Java、objective C等面向对象语言都提供了接口的概念, 在C++中可以通过抽象类来模拟接口的行为。

与此同时,C++通过虚函数实现了多态:通过基类指针或引用调用虚函数时,会调用当前对象的实际类型中声明的函数。 为了这个特性,包含虚函数的C++对象中会存储一个虚函数表指针,来完成动态联编。

编译程序在编译阶段并不能确切知道将要调用的函数,只有在程序运行时才能确定将要调用的函数, 为此要确切知道该调用的函数,要求联编工作要在程序运行时进行, 这种在程序运行时进行联编工作被称为动态联编

jQuery事件:bind、delegate、on的区别

AngularJS DOM HTML JavaScript jQuery 事件

最近在AngularJS的开发中,遇到一个神奇的事情:我们用到livebox来预览评论列表中的图片, 然而评论列表是由Angular Resource动态载入的。不可思议的是,点击这些动态载入的图片仍然会触发lightbox的图片预览。 难道lightbox使用先进的MutationObserver技术监听了DOM的变化?观察lightbox源码才发现,原来只是jQuery的.on()方法:

$('body').on('click', 'a[rel^=lightbox], ...', function(event){});

本文便来详解各种jQuery事件绑定方法:onbinddelegateliveunbindtrigger。 同时总结一下常用的jQuery事件技术:如何阻止事件冒泡、阻止浏览器默认行为、解绑事件处理函数、自定义事件。

什么是 jQuery 事件

jQuery事件是DOM事件的封装,同时支持自定义的扩展。在程序设计中,事件和代理有着相似的作用: 它们提供了一种机制,使得行为的实现方式和调用时机可以分离。

不谈jQuery,DOM本身就提供了一系列的javascript事件,例如clickkeyupsubmit。 未实现相关业务逻辑,通常会为这些事件定义一系列的处理函数,处理函数定义了业务的实现方式,而浏览器知道这些业务的调用时机。 Javascript事件就是这样一种机制,使得行为的实现方式和调用时机可以动态地绑定。

jQuery事件是通过封装javascript事件来实现的,例如.keyup()便是onkeyup的封装:

.keyup(): Bind an event handler to the "keyup" JavaScript event, or trigger that event on an element.

除了封装大多数的javascript事件,jQuery提供了统一的事件绑定和触发机制:

  • 绑定事件:bindonlivedelegatekeyup(<function>)
  • 触发事件:trigger('keyup')keyup()
  • 解绑事件:unbindoffdieundelegate

事件绑定:bind

使用javascript绑定一个事件很简单,只需要在HTML中设置onxxx属性, 并且在javascript中定义相关的处理函数便可以完成。

<div onclick="func()"></div>
<script>
function func(){
    console.log('clicked!');
}
</script>

上述是基本的javascript事件处理方式,而jQuery提供了更加方便的方式:.bind()函数。

.bind():Attach a handler to an event for the elements.

<div id='foo'></div>
<script>
$('#foo').click(function(){
    console.log('clicked!');
});
</script>

.click(<function>)等效于.bind('click', <function>)。另外还可以通过unbind来解绑事件:

$('#foo').unbind('click');

如果unbind参数为空,则解绑匹配元素的所有事件处理函数。 在我的理解中,我们还是不要offunbinddie吧。即使不谈效率,它们也使得软件更难理解了。 如果你感觉有需要,下面的.on()应该会满足你~

.bind将会给所有匹配的元素都绑定一次事件,当元素很多时性能会变差。 而且后来动态新增的元素不会被绑定。

C++手稿:运算符重载

C++ 引用 运算符 成员函数 构造函数 链式调用 赋值运算符 运算符重载

运算符重载就是对已有的C++运算符赋予更多的语义,让一个运算符可以作用于其他的数据类型。 典型地,让运算符接受一个类的对象作为参数。

通常有两种方式来重载一个运算符:

  1. 声明一个普通函数,作为类的友元。
  2. 声明为类的成员方法。

事实上,运算符的本质是函数。每个运算符调用会转换为函数调用,运算符的操作数转换为函数参数。 运算符的重载本质上是方法的重载。

这些运算符不允许重载:..*::?:sizeof

重载为普通函数

重载为普通函数时,参数的个数为运算符的目数:

CPerson operator+(const CPerson& male, const CPerson& female){
    return CPerson(male.name + female.name);
};

CPerson male("Bob"), female("Alice");
CPerson child1 = male + female;
// 等价于
CPerson child2 = operator+(male, female);

这些运算符必须重载为成员函数:()[]->=

重载为成员函数

重载为成员函数时,参数的个数为运算符的目数-1:

class CPerson{
    string name;
public:
    CPerson(string name_):name(name_){}
    CPerson operator+(const CPerson& female){
        return CPerson(name + female.name);
    }
};

CPerson male("Bob"), female("Alice");
CPerson child1 = male + female;
// 等价于
CPerson child2 = male.operator+(female);

上一页 下一页