C++入门(类和对象一篇通)

Posted 皓仔活在今天

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++入门(类和对象一篇通)相关的知识,希望对你有一定的参考价值。

文章目录

类和对象

1、类的构建

C++兼容C struct的用法
C++同时对struct进行了升级,把struct 升级成了类

  • 结构体名称可以做类型
  • 里面可以定义函数
  • 类有两种定义方式,一种是struct定义类,另一种是class定义类,两者的区别是,class定义的类是私有的不能在类外面访问,struct定义的类是公有的可以在类外面访问
  • C++还新加入了三种访问限定符号:1、public(公有限定符号),2、private(私有限定符号),3、protected(保护限定符号),如果是struct定义的类,默认是public,如果是class定义的类,默认是private
  • 访问限定符的作用范围是从此访问限定符开始,到下一个访问限定符结束
// struct 不加访问限定符,默认是public
// class 不加访问限定符,默认是private
class Student

public://公有限定符号
   // 类体:由成员函数和成员变量组成
   void Init(const char* name, const char* gender, int age)
   
	   strcpy(_name, name);
	   strcpy(_gender, gender);
	   _age = age;
   

   void Print()
   
	cout << _name << " " << _gender << " " << _age << endl;
   


	// 这里并不是必须加_
	// 习惯加这个,用来标识成员变量
private://私有限定符号
	char _name[20];
	char _gender[3];
protected://保护限定符号
	int _age;
;

2、封装

  • 封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。
// 封装:更严格管理设计
// 1、数据和方法封装到一起,类里面
// 2、想给你自由访问的设计成共有,不想给你直接访问的设计成私有
// 一般情况设计类,成员数据都是私有或者保护
//想给访问的函数是共有,不想给你访问时私有或保护
class Stack

private://私有限定符
	void Checkcapaicty()
	
public://公有限定符
	void Init()//用户可以调用的函数接口
	

	void Push(int x)
	

	int Top()
	

private://私有限定符保护对象
	int* _a;
	int _top;
    int _capacity;
;

3、类的作用域

  • 类定义了一个新的作用域,类的所有成员都在类的作用域中。在类体外定义成员,需要使用 : : 作用域解析符指明成员属于哪个类域。
//两个类域
//class Stack和class Queue中的push函数是可以同时存在的
//他们不构成函数重载的原因是,函数重载在同一个作用域中才会发生
//此时两个push函数是在Stack和Queue两个不同的类的类域中
class Stack

public:
	void Push(int x)
	
;

class Queue

public:
	void Push(int x)
	
;

4、类的两种定义方式

1、在类中定义函数,这样定义的函数自动被视为内联函数

class Stack

public:
	// 在类里面定义
	// 在类里面定义的函数默认是inline
	void Init()
	
		_a = nullptr;
		_top = 0;
		_capacity = 0;
	

	// 在类里面声明
	void Push(int x);
	void Pop();
	// 总结一下:实际中,一般情况下,短小函数可以直接在类里面定义
	//长一点函数声明和定义分离
	
//private:
//这个地方是变量的声明,判断变量声明还是定义,就看他有没有开辟空间
	int* _a;
	int _top;
	int _capacity;
;

2、在类中声明函数,在其他地方实现这种方式需要使用 : : 作用域解析符

void Stack::Push(int x)//作用域解析符
//把push从Stack的类域中放出来

	// ...
	_top++;

5、类的大小计算

  • 一个类的大小,实际就是该类中”成员变量”之和,当然也要进行内存对齐,注意空类的大小,空类比较特殊,编译器给了空类一个字节来唯一标识这个类。
// 类中既有成员变量,又有成员函数
//A1类的大小为8个字节,int类型成员变量和char类型成员变量内存对齐
class A1 
public:
	void f1()
private:
	int _a;
	char _ch;
;

// 总结:没有成员变量的类对象,编译会给他们分配1byte占位
//表示对象存在过 

// 类中仅有成员函数(类的大小为1个字节)
class A2 
public:
	void f2() 
;

// 类中什么都没有---空类(类的大小为1个字节)
class A3

	//char _ch;
;

6、隐含的this指针

  • 在类中函数的形参位置,C++设置了一个隐含的this指针,我们不能将它用显式的方法编写出来,这是在形参位置利用隐含的this指针是编译器的工作
  • this指针可以为空(不能用空指针解引用成员变量)
  • this指针是存在上的(函数栈帧),有些编译器会将this放到寄存器里面进行优化
class Date

public:
	/*void Print(Date* const this)//这是编译器编译后修改的版本
	//在形参位置设置了一个隐含的this指针
	
		cout << this->_year << "-" << this->_month << "-" << this->_day << endl;
	*/

	void Print()
	
		cout << this << endl;
		cout << this->_year << "-" << this->_month << "-" << this->_day << endl;
	
    这是编译器编译后修改的版本
	/*void Init(Date* const this, int year, int month, int day)
	
	this->_year = year;
	this->_month = month;
	this->_day = day;
	*/

	void Init(int year, int month, int day)
	
	//this = nullptr; this指针本身不能修改,因为他是const修饰的
	// this指向对象可以被修改
		cout << this << endl;

		this->_year = year;
		_month = month;
		_day = day;
	
private:
	int _year; // 年
	int _month; // 月
	int _day; // 日
;

7、类的6个默认成员函数(重点)

  • 学习默认成员函数的几个要点
  • 基本语法特性、函数名、参数、返回值、什么时候调用
  • 我们不写编译器默认生成这个函数干了什么事情

7.1、构造函数

  • 构造函数特殊的成员函数,需要注意的是,构造函数的虽然名称叫构造,但是需要注意的是构造函数的主要任务并不是开空间创建对象,而是初始化对象
  • 构造函数的特点:1. 函数名与类名相同。2. 无返回值。3. 对象实例化时编译器自动调用对应的构造函数。4. 构造函数可以函数重载。
class Date

public:
	Date()//构造函数无参数版本(如果我们不写,编译器会自动生成)
	
		_year = 1;
		_month = 1;
		_day = 1;
	

	Date(int year, int month, int day)//构造函数有参数版本
	
		_year = year;
		_month = month;
		_day = day;
	

	Date(int year = 1, int month = 1, int day = 1)//构造函数的全缺省写法
	
		_year = year;
		_month = month;
		_day = day;
	
	
	void Print()
	
		cout << _year << "-" << _month << "-" << _day << endl;
	
	private:
	int _year; // 年
	int _month; // 月
	int _day; // 日
;

int main()

	//Date d1(); // 不能这么写
	Date d1;//构造函数的调用
	d1.Print();

	Date d2(2022, 5, 15);//可以全传参数调用
	d2.Print();
	
	Date d3(2022);//也可以只传一两个参数缺省调用
	d3.Print();

	Date d4(2022, 10);//这也是缺省调用
	//构造函数的名字和类名相同
	d4.Print();

	return 0;

  • 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。(意思是如果我们不写构造函数,编译器会默认生成一个无参构造函数)
    - 重点:C++将类型分为两种
    1、内置类型/基本类型:int/char/double/指针…
    2、自定义类型:class/struct去定义类型对象
    (默认生成构造函数对于内置类型成员变量不做处理,对于自定义类型成员变量才会处理,调用这个自定义类型成员的默认构造函数)
    3、总结:如果一个类中的成员变量全是自定义类型,并且这些自定义类型成员都提供了默认构造函数,我们就可以不用写构造函数,让编译器默认生成的无参构造函数。如果还有内置类型的成员,需要在声明时给缺省值(所以大多数情况下我们都自己写构造函数,不用默认生成的)

  • 在C++定义中,无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认成员函数。(总而言之就是不用传参就能调用的构造函数,都被定义为默认构造函数)

7.2、析构函数

  • 析构函数:与构造函数功能相反,析构函数不是完成对象的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成类的资源清理工作(例如malloc申请的空间)
  • 析构函数的特点:1. 析构函数名是在类名前加上字符 ~。2. 无参数无返回值。3. 一个类有且只有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。
class Stack

public:
	// Init(构造函数类似于我们之前写的初始化函数)
	// Destroy(析构函数类似于之前学习的销毁函数)

	Stack(int capacity = 10)//全缺省构造函数(默认构造函数的一种)
	
		_a = (int*)malloc(sizeof(int)*capacity);
		assert(_a);

		_top = 0;
		_capacity = capacity;
	

	~Stack()//默认析构函数
	
		cout << "~Stack():" << this << endl;

		free(_a);
		_a = nullptr;
		_top = _capacity = 0;
	
private:
	int* _a;
	int _top;
	int _capacity;
	;

7.3、拷贝构造函数

  • 拷贝构造函数也是特殊的成员函数,其特征如下:1. 拷贝构造函数是构造函数的一个重载形式。2. 拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用。
  • 同类型对象传值传参需要调用拷贝构造
  • 自定义类型的初始化都要调用拷贝构造
// 我们不写编译器会默认生成一个拷贝构造
// 1、内置类型的成员会完成值拷贝,浅拷贝。
//浅拷贝的问题:如果成员变量有开辟空间(例如数据结构栈)
//那么浅拷贝会导致拷贝前后的两个变量指向同一块空间
//(修改数据会互相影响,而且同一块空间在析构时会析构两次,程序会崩溃)

// 2、自定义类型的成员,去调用这个成员的拷贝构造
//结论:一般的类,自己生成的拷贝构造就够用了
//只有像栈这样的类,自己直接管理资源,自己写一个拷贝构造(需要实现深拷贝)
Date(const Date& d)

	_year = d._year;
	_month = d._month;
	_day = d._day;

7.4、运算符重载

  • 内置类型,可以直接用各种运算符。自定义类型,不能直接用各种运算符,为了自定义类型可以使用各种运算符,C++设立了运算符重载的规则
  • 注意:
  • 不能通过连接其他符号来创建新的操作符:比如operator@
  • 重载操作符必须有一个类类型或者枚举类型的操作数
  • 用于内置类型的操作符,其含义不能改变,例如:内置的整型“+”,不能实现成 “-”。
  • 作为类成员的重载函数时,其形参看起来比操作数数目少1成员函数的操作符有一个默认的形参this,限定为第一个形参
  • .* 、:: 、sizeof 、?: 、. 注意以上5个运算符不能重载。这个经常在笔试选择题中出现。
  • 有意义的运算符才会重载
//运算符重载 -- 函数
//函数名:operator 运算符
//参数:运算符的操作数
//返回值:运算符运算后结果

class Date

public:
// 默认生成的析构函数,内置类型成员不做处理,自定义类型成员会去调用它的析构函数
Date(int year = 1, int month = 1, int day = 1)

	_year = year;
	_month = month;
	_day = day;


void Print()

	cout << _year << "-" << _month << "-" << _day << endl;


//运算符重载函数一般都直接写在类里
bool operator==(const Date& d)//运算符重载函数
// bool operator==(Data* const this,Data d)
// “==”号是双目操作符却只有一个参数的原因就是类里面会自动处理生成一个this指针

	return _year == d._year
		&& _month == d._month
		&& _day == d._day;


// 2022 5 16
// 2021 10 16
bool operator<(const Date& d)//运算符重载函数
//利用引用操作符,就可以不调用拷贝构造
//用const修饰引用,让引用不改变(防止出现d._year == year 的错误,这样写会报错)

	if ((_year < d._year)
		|| (_year == d._year && _month < d._month)
		|| (_year == d._year && _month == d._month && d._day < d._day))
    
	    return true;
    
	else
	
		return false;
	


private:
    int _year;
	int _month;
	int _day;
;

//运算符重载函数调用方法
if (d1.operator==(d2))

	cout << "==" << endl;


if (d1 == d2) // 编译器会处理成对应重载运算符调用 if (operator==(d1, d2))

	cout << "==" << endl;

7.5、赋值重载

  • 参数类型
  • 返回值
  • 检测是否自己给自己赋值
  • 返回*this
  • 一个类如果没有显式定义赋值运算符重载,编译器也会生成一个,完成对象按字节序的值拷贝(浅拷贝)。
class Date

public:
    //d2 = d1; -> d2.operator=(&d2, d1)
	// d1 = d1
	Date& operator=(const Date& d)//这里使用传引用返回,如果传值返回还需要拷贝
	//自定义类型拷贝需要调用拷贝构造,代价较大,所以使用传引用返回
	
		if (this != &d)//判断是不是自己给自己赋值
		
			_year = d._year;
			_month = d._month;
			_day = d._day;
		

		return *this;//解引用this指针
    
private:
	int _year;
	int _month;
	int _day;
;

int main()

	Date d1(2022, 5, 16);
	Date d2(2022, 5, 18);

	Date d3(d1); // 拷贝构造  -- 一个存在的对象去初始化另一个要创建的对象
	d3 = d2 = d1;     // 赋值重载/复制拷贝 -- 两个已经存在对象之间赋值
	(d3 = d2) = d1;     // 赋值重载/复制拷贝 -- 两个已经存在对象之间赋值

	d1 = d1;//自己给自己赋值的例子

	int i = 0, j, k;
	k = j = i;//赋值操作符是有返回值的
	//(k = j) = i;

	return 0;

8、总结

//用日期类型做个总结
class Date

public:
	int GetMonthDay(int year, int month)//判断某年某月有多少天的函数
	
		int monthDayArray[13] =  0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ;
		if (month == 2 && isLeapYear(year))
		
			return 29;
		
		else
		
			return monthDayArray[month];
		
	

	// 默认生成的析构函数,内置类型成员不做处理,自定义类型成员会去调用它的析构函数
	Date(int year = 1, int month = 1, int day = 1)
	
		if (year >= 1 && 
			month <= 12 && month >= 1 && 
			day >= 1 && day <= GetMonthDay(year, month))
		
			_year = year;
			_month = month;
			_day = day;
		
		else
		
			cout << "日期非法" << endl;
		
	

	void Print()
	
		cout << _year << "-" << _month << "-" << _day << endl;
	

	// d1 + 100; -> d1.operator+(int day)
	// d1 - 100;
	// d1 - d2;
	Date operator+(int day);
	Date operator-(int day);
	int operator-(const Date& d);

	//bool operator>(const Date& d)
	//bool operator>=(const Date& d)
	//bool operator==(const Date& d)
	//bool operator!=(const Date& d)
	//bool operator<=(const Date& d)
	bool operator<(const Date& d)
	
		if ((_year < d._year)
			|| (_year == d._year && _month < d._month)
			|| (_year == d._year && _month == d._month && d._day < d._day))
		
			return true;
		
		else
		
			return false;
		
	

private:
	int _year;
	int _month;
	int _day;
;

Cython 一篇通

Cython的类型

1 类型定义

1.1 定义一个C变量:

1.1.1 在Cython里定义一个C变量和C语言类似,不同的地方就是在声明的最前面要加上cdef,另外,末尾不用加分号";“如:

cdef int an[10]

cdef int n = 123

cdef int *pn = &n

printf("%d \n",pn[0])

 

 

 

 

1.1.2 这里要注意的是,以Cython里不能用类似*ptr这样代码来对指针变量进行取值,而必须用ptr[0]这样的形式,如上面的例子中,printf("%d\n",*pn)是编译通不过的

1.2 定义一个Python对象

1.2.1 和Python没有区别,直接给一个变量名赋值即可,如:

b = [1,2,3]

a = ‘hello,world‘

1.2.2 每个变量名实现上都是一个Python对象指针

2 类型转换

2.1 在Cython里用<>替代了()来进行类型转换,如:

cdef float a= 123.456

cdef int b

b = <int>a

 

2.2 再如:

cdef int n

a = ‘hello,world‘

n = <int>a

2.2.1 这里要注意的是:n = <int>a 把一个Python字符串对象指针强制转换成了一个整形变量,这在封装回调函数时经常会用到强制将一个Python对象指针强制转换成C类型的情况。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Cython的函数

1 函数定义:

1.1 在Cython里定义一个类C函数:

1.1.1 在这里之所以说是定义一个"类C”函数,而不是一个C函数,是因为它和纯C函数的定义还是有区别的,具体看下面的例子:

1.1.2 例1:

cdef int Max(int a,int b):

    if a>b:

       return a

    else:

       return b

 

可见,定义一个C函数和纯C还是比较相似的

但是语法却是遵循python的语法

1.1.3 再来看成一个比较怪异的例子,例2:

cdef Max(int a,b):

    if a>b:

        return a

    else:

        return b

这个例子就有点怪了,牛不是牛,马不像马,还是从翻译出来的c代码中去找答案吧:

先创建一个test.pyx

cdef Max(int a,b):

    if a>b:

        return a

    else:

        return b

 

再转成C代码:

cython -o test.c test.pyx

打开test.c,搜索cdef Max定义到下面的代码:

 

/* "test.pyx":1

 * cdef Max(int a,b):            #<<<<<<<<<<<<<<

 *     if a>b:

 *         return a

 */

 

static PyObject *__pyx_f_4test_Max(int __pyx_v_a, PyObject *__pyx_v_b) {

...

 

总结一下:

如果一个函数的参数或者返回值没有指定类型,那么它就是一个python对象,在进行操作时会将python对象转换成相应的C类型

如果参数或者返回值指定了类型,那么不会进行任何转换

1.1.4 再来看一个例子,例3

cdef object Max(int a,object b):

    if a>b:

        return a

    else:

        return b

和上一个例子不同的地方就是加了object关键,这表明这是一个python对象

它其实和上面生成的C代码是一样的,

这也验证了上面那个例子的理解是正确的

1.1.5 一个问题,如何在C代码里调用Cython里定义的C函数呢?

有时候,我们不仅仅是需要在Python代码里调用C代码,还有可能需要在Cython之外中的C代码里里调用Python的代码,

而我们知道,在Cython里定义的C函数是可以直接调用Python代码的

这样,可以通过在Cython外的C函数里调用Cython里定义的C函数来达到在C代码里调用Python代码的目的

但是,从上面的例子来看,经过Cython的翻译之后,函数名称被改变了,并且函数是static的,对外不可见,是不是无法调用?

答案很否定的,只要将Cython中定义的C函数声明为public或者extern即可,具体的来例子:

cdef public int Max(int a,int b):

    if a>b:

        return a

    else:

        return b

Cython翻译成C代码后:

 * cdef public int Max(int a,int b):            #<<<<<<<<<<<<<<

 *     if a>b:

 *         return a

 */

 

int Max(int __pyx_v_a,int __pyx_v_b) {

...

 

这样,这个函数就可以另外的C代码中补调用了。

但是,调用是可以调用,但是如果这个C函数里又调用了Python代码的话,在C代码里直接调用这个函数,程序会崩溃,这个另外的篇幅中再详细的讨论。

1.2  在Cython里定义一个类Python函数:

1.2.1 和类C函数的定义相似,只是以def开头,而不是以cdef开头

1.2.2 不同的地方是不能指定返回值的类型,这个很好理解,Python的函数肯定返回的是一个Python对象了,不可能返回一个C类型

1.2.3 即使你在Python里没有调用return,或者没有return一个对象,Python也默认会返回一个None对象

2 让类C函数在出错时抛出异常

2.1 这里有两种出错的情况

2.1.1 一种是C函数返回一个错误值

2.1.2 另外一种情况就是C函数里调用的Python代码出现的异常

2.2 首先来看一个例子:

cdef int ExceptTest(int n):

    a = "abc"

    print a.length()

    return n

 

def CallExceptTest(intn):

    return ExceptTest(n)

 

2.2.1 这里故意调用了一个字符串对象不存在的方法length

Subtopic

2.2.2 运行例子:

>>> import test

>>> test.CallExceptTest(-1)

Exception AttributeError: "‘str‘object has no attribute ‘length‘" in ‘test.ExceptTest‘ ignored

0

2.2.3 可见错误被忽略了

2.3 如何让C函数抛出异常呢?还是先看例子:

import traceback

import sys

 

cdef int ExceptTest() except -1:

    a = "abc"

    print a.length()

    return 1

 

def CallExceptTest():

    try:

       ExceptTest()

    except Exception:

        print "Exceptionin user code:"

       traceback.print_exc(file=sys.stdout)

 

2.3.1 这个例子比上个例子在函数的声明时加上了 except -1

2.3.2 意思是异常产生时返回-1

2.3.3 测试一下:

>>> import test;test.CallExceptTest()

Exception in user code:

Traceback (most recent call last):

  File "test.pyx", line 14,in test.CallExceptTest (test.c:809)

    ExceptTest()

  File "test.pyx", line 9,in test.ExceptTest (test.c:718)

    print a.length()

AttributeError: ‘str‘ object has noattribute ‘length‘

>>> 

可见,这可比上个例子中只打印只一句冷冰冰的ignore好多了

通过异常时打出来的堆栈信息,可以定位到具体在test.pyx的哪一行出的错

这是不是比没有延时异常时好多了?

2.4 再来看下如果不是在C函数中调用的Python中出错会怎样:

import traceback

import sys

 

cdef int ExceptTest() except -1:

    return -1

 

def CallExceptTest():

    try:

       ExceptTest()

    except Exception:

        print "Exceptionin user code:"

       traceback.print_exc(file=sys.stdout)

2.4.1 这个例子中直接返了了-1

2.4.2 测试一下:

>>> importtest;test.CallExceptTest()

Traceback (most recent call last):

  File "<stdin>", line1, in <module>

SystemError: errorreturn without exception set

2.4.3 直接抛出了一个SystemError,也没有堆栈信息输出

2.5 问题:有没有办法只要想在C函数中调用Python代码出错时抛出异常呢

2.5.1 答案当然是肯定的,在编程的世界里,永远都是只有想不到的,没有做不到的,

2.5.2 只要将except -1改成except? -1即可,

2.5.3 这样Cython就会在函数返回-1时调用PyErr_Occurred()来判断是否真的有一个异常产生,从而避免上个例子中出现的情况。

2.6 如果函数是void型,没有返回值,怎么让函数返回异常?

2.6.1 很简单,用except* 即可

如 cdef void Test() except* 

 

 

 

 

 

 

 

Cython应用手记

作者: 日期:

gashero
2010-03-29

目录

  • 1   简介
  • 2   基本使用
  • 3   调用其他C库
    • 3.1   简单例子
    • 3.2   重新定义外部C库的定义
  • 4   类定义
  • 5   与Python交互

1   简 介

一种为Python写C扩展的方式,尝试一下。

参考文献:

  1. [r] 官方主页: http://www.cython.org/
  2. [r] Cython三分钟入门: http://blog.csdn.net/lanphaday/archive/2009/09/17/4561611.aspx
  3. [u] A quick Cython introduction: http://www.perrygeo.net/wordpress/?p=116 其实就是上文的原文
  4. [i] Cython‘s Documentation: http://docs.cython.org/ 看到"Extensioin types"

2   基 本使用

Cython基于pyrex,但是拥有更多功能和优化。用来写Python的C扩展的,并生成有效的C代码。写出的文件扩展名是 ".pyx" ,已经可以算作一种语言了。

一个简单的加法函数( addtest.pyx ):

def addtest(a,b):
    cdef float c=a+b
    return c

编译和生成动态库:

cython addtest.pyx
gcc -c -fPIC -I/usr/include/python2.5 addtest.c
gcc -shared addtest.o -o addtest.so

使用:

$ python
>>> import addtest
>>> addtest(1,2)
3.0

构建Cython代码的方式:

  1. 使用 setup.py ,常用
  2. 使用pyximport导入 ".pyx" 文件
  3. 运行cython命令编译出.c文件后再编译成.so文件
  4. 使用Sage

使用 setup.py 方式,例如一个 hello.pyx 文件,编写的 setup.py 如下:

from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext

ext_modules=[Extension(‘hello‘,[‘hello.pyx‘])]

setup(
    name=‘Hello world app‘,
    cmdclass={‘build_ext‘:build_ext},
    ext_modules=ext_modules
)

构建使用命令 python setup.py build_ext --inplace 

Cython提高速度的主要原因是使用静态类型。可以在任何参数前直接使用C的类型定义。函数内的话要加"cdef"前缀。如:

def f(double x):
    cdef double ret
    ret=x**2-x
    return ret

仅仅使用Cython编译纯Python代码可以提高35%的性能,几乎全部使用静态类型以后提高4倍。

C风格函数声明,"except? -2"表示返回-2时就是出错了。不过"except *"是肯定安全的。如:

cdef double f(double) except? -2:
    return x**2-x

使用cpdef时,这个函数就可以同时被C和Python调用了。当使用了C函数时,因为避开了昂贵的函数调用,旺旺可以提高150倍的速度。

不要过度优化,一步步的优化并且查看profile。使用"cython -a"参数可以查看HTML报告。

3   调 用其他C库

3.1   简 单例子

导入"math.h"中的 sin() 函数并使用:

cdef extern from "math.h":
    double sin(double)

cdef double f(double x):
    return sin(x*x)

Cython不会去扫描头文件,所以自己必须再声明一遍。下面是使用时必须连接上其他库的 setup.py 文件:

from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext

ext_modules=[
    Extension(‘demo‘,[‘demo.pyx‘,],libraries=[‘m‘,])
    ]

setup(
    name=‘Demos‘,
    cmdclass={‘build_ext‘:build_ext},
    ext_modules=ext_modules,
)

同理可以使用任何动态或静态编译的库。

3.2   重 新定义外部C库的定义

一段C代码,头文件中的类型定义与函数声明:

typedef struct _Queue Queue;
typedef void *QueueValue;

Queue *queue_new(void);
void queue_free(Queue *queue);

int queue_push_head(Queue *queue, QueueValue data);
QueueValue queue_pop_head(Queue *queue);
QueueValue queue_peek_head(Queue *queue);

int queue_push_tail(Queue *queue, QueueValue data);
QueueValue queue_pop_tail(Queue *queue);
QueueValue queue_peek_tail(Queue *queue);

int queue_is_empty(Queue *queue);

对应的Cython定义,写入一个".pxd"文件中:

cdef extern from "libcalg/queue.h":
    ctypedef struct Queue:
        pass
    ctypedef void* QueueValue

    Queue* new_queue()
    void queue_free(Queue* queue)

    int queue_push_head(Queue* queue, QueueValue data)
    QueueValue  queue_pop_head(Queue* queue)
    QueueValue queue_peek_head(Queue* queue)

    int queue_push_tail(Queue* queue, QueueValue data)
    QueueValue queue_pop_tail(Queue* queue)
    QueueValue queue_peek_tail(Queue* queue)

    bint queue_is_empty(Queue* queue)

大部分时候这种声明与头文件几乎是一样的,你可以直接拷贝过来。唯一的区别在最后一行,C函数的返回值其实是布尔值,所以用bint类型会转换成 Python的布尔值。

这里可以不关心结构体的内容,而只是用它的名字。

4   类 定义

一个类的例子:

cimport cqueue
cimport python_exc

cdef class Queue:
    cdef cqueue.Queue_c_queue
    def __cinit__(self):
        self._c_queue=cqueue.new_queue()

这里的构造函数是 __cinit__() 而不是 __init__() 。虽然 __init__() 依然有效,但是并不确保一定会运行(比如子类忘了调用基类的构造函数)。因为未初始化的指针经常导致Python挂掉而没有任何提示,所以 __cinit__() 总是会在初始化时调用。不过其被调用时,对象尚未构造完成,所以除了cdef字段以外,避免其他操作。如果要给__cinit__() 构造和函数加参数,必须与 __init__() 的匹配。

构造函数初始化资源时记得看看返回的资源是否是有效的。如果无效可以抛出错误。Cython提供了内存不足异常,如下:

def __cinit__(self):
    self._c_queue=cqueue.new_queue()
    if self._c_queue is NULL:
        python_exc.PyErr_NoMemory()

Cython提供的析构函数,仅在建立成功内部对象时释放内部对象:

def __dealloc__(self):
    if self._c_queue is not NULL:
        cqueue.queue_free(self._c_queue)

将数据以通用指针方式进入,和返回时的强制类型转换:

cdef append(self,int value):
    cqueue.queue_push_tail(self._c_queue,<void*>value)

cdef int pop(self):
    return <int>cqueue.queue_pop_head(self._c_queue)

Cython除了支持普通Python类以外,还支持扩展类型,使用"cdef class"定义。在内存占用和效率上更好。因为使用C结构体存储字段和方法,而不是Python字典。所以可以存储任意C字段类型,而不是其 Python包装。访问时也是直接访问C的值,而不是通过字典查找。

普通的Python类可以继承自cdef类,但是反之则不行。Cython需要知道完整的继承层次来定义C结构体,并且严格限制单继承。不过普通 Python类可以继承任一数量的Python类和扩展类型,无论在Python中还是在Cython代码中。

5   与 Python交互

如果Cython调用Python函数失败,则直接返回NULL,而不是异常对象。

如果一个函数既有可能返回NULL,也有可能返回0,则处理起来就比较麻烦。Python C API的做法是PyErr_Occurred() 函数。不过这种方式有性能损耗。在Cython中你可以自己指定哪个返回值代表错误,所以环境只要检查这个返回值即可。其他所有值都回无损耗的被接受。

在函数定义时指定except子句,则仅在函数返回该值时检查是否需要抛出异常。这样同一个函数返回0和返回0同时返回错误就可以区分开。例子:

cdef int pop(self) except? 0:
    #...

类中的 cdef 定义C方法,而 cpdef 可以同时定义C方法和Python方法。

 

以上是关于C++入门(类和对象一篇通)的主要内容,如果未能解决你的问题,请参考以下文章

C++从入门到入土第二篇:类和对象基础

C++从入门到入土第四篇:运算符重载

Python之路,第一篇:Python入门与基础

C++入门系列1从C到C++:C++类和对象到底是什么意思?

C++入门基础教程:类和对象(中)

C++入门基础教程:类和对象(上)