文章目录
C++
内存空间说明
内存空间类型
- 代码段(正文段):代码段(code segment/text segment)通常是指用来存放程序执行代码的一块内存区域。这部分区域的大小在程序运行前就已经确定,并且内存区域通常属于只读, 某些架构也允许代码段为可写,即允许修改程序。在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等。
- 数据段:数据段(data segment)通常是指用来存放程序中已初始化的全局变量的一块内存区域。数据段属于静态内存分配。
- BSS段:BSS段(bsssegment)通常是指用来存放程序中未初始化的全局变量的一块内存区域。BSS是英文Block Started by Symbol的简称。BSS段属于静态内存分配。
- 堆(heap):堆是用于存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩张或缩减。当进程调用malloc等函数分配内存时,新分配的内存就被动态添加到堆上(堆被扩张);当利用free等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减)
- 栈(stack):栈又称堆栈, 是用户存放程序临时创建的局部变量,也就是说我们函数括弧“{}”中定义的变量(但不包括static声明的变量,static意味着在数据段中存放变量)。除此以外,在函数被调用时,其参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值也会被存放回栈中。由于栈的先进先出特点,所以栈特别方便用来保存/恢复调用现场。从这个意义上讲,我们可以把堆栈看成一个寄存、交换临时数据的内存区。
例子
- 函数代码存放在代码段。声明的类如果从未使用,则在编译时,会优化掉,其成员函数不占代码段空间。
- 全局变量或静态变量,放在数据段,注:按理来说 ,没有声明的全局变量或静态变量应该保存在BSS中,但结果看出感觉它们在一块
- 局部变量放在栈中,
- 用new产生的对象放在堆中,
- 常量保存在常量区,常量区地址比数据段地址更低
- CPU寄存器:CPU寄存器,其实就是来控制代码段和数据段的指令及数据读取的地方,当然,CPU也有自己存放数据的地方,那就是通用寄存器里的数据寄存器,通常是EDX寄存器,C语言里有个register,就是把数据放在这个寄存器里,这样读取数据就相当的快了,因为不用去内存找,就省去了寻址和传送数据的时间开销。
- 还有一些寄存器是用来指示当前代码段的位置、数据段的位置、堆栈段的位置等等(注意这里存放的只是相应的代码或数据在内存中的地址,并不是实际的值,然后根据这个地址,通过地址总线和数据总线,去内存中获取相应的值),不然在执行代码的时候,指令和数据从哪取呢?还有标志寄存器,用来标识一些状态位,比如标识算术溢出呀等。寄存器是特殊形式的内存,嵌入到处理器内部。
- 每个进程需要访问内存中属于自身的区域,因此,可将内存划分成小的段,按需分发给进程。
- 寄存器用来存储和跟踪进程当前维护的段。偏移寄存器(Offset Registers)用来跟踪关键的数据放在段中的位置。在进程被载入内存中时,基本上被分裂成许多小的节(section)。我们比较关注的是6个主要的节:
(1) .text 节
.text 节基本上相当于二进制可执行文件的.text部分,它包含了完成程序任务的机器指令。
该节标记为只读,如果发生写操作,会造成segmentation fault。在进程最初被加载到内存中开始,该节的大小就被固定。
(2).data 节
.data节用来存储初始化过的变量,如:int a =0 ; 该节的大小在运行时固定的。
(3).bss 节
栈下节(below stack section ,即.bss)用来存储为初始化的变量,如:int a; 该节的大小在运行时固定的。
(4) 堆节
堆节(heap section)用来存储动态分配的变量,位置从内存的低地址向高地址增长。内存的分配和释放通过malloc() 和 free() 函数控制。
(5) 栈节
栈节(stack section)用来跟踪函数调用(可能是递归的),在大多数系统上从内存的高地址向低地址增长。
同时,栈这种增长方式,导致了缓冲区溢出的可能性。
(6)环境/参数节:环境/参数节(environment/arguments section)用来存储系统环境变量的一份复制文件,
进程在运行时可能需要。例如,运行中的进程,可以通过环境变量来访问路径、shell 名称、主机名等信息。
该节是可写的,因此在格式串(format string)和缓冲区溢出(buffer overflow)攻击中都可以使用该节。
另外,命令行参数也保持在该区域中.
简介
- 一种静态类型的(编译时进行类型检查)、编译式的、通用的、大小写敏感的、不规则的编程语言,支持过程化编程、面向对象编程和泛型编程。
- 中级语言
- C++是C的超集
- 面向对象编程:
封装/抽象/继承/多态
- 标准C++三部分组成:
核心语言
,提供了所有构件块,包括变量、数据类型和常量等;C++ 标准库
,提供了大量的函数,用于操作文件、字符串等;标准模板库(STL)
,提供了大量的方法,用于操作数据结构等 - ANSI标准:确保 C++ 的便携性 —— 您所编写的代码在 Mac、UNIX、Windows、Alpha 计算机上都能通过编译
- 基本概念:
- 对象 - 对象具有状态和行为。例如:一只狗的状态 - 颜色、名称、品种,行为 - 摇动、叫唤、吃。对象是类的实例。
- 类 - 类可以定义为描述对象行为/状态的模板/蓝图。
- 方法 - 从基本上说,一个方法表示一种行为。一个类可以包含多个方法。可以在方法中写入逻辑、操作数据以及执行所有的动作。
- 即时变量 - 每个对象都有其独特的即时变量。对象的状态是由这些即时变量的值创建的
- 左值和右值:
左值(lvalue)
:指向内存位置的表达式被称为左值(lvalue)表达式。左值可以出现在赋值号的左边或右边。;右值(rvalue)
指的是存储在内存中某些地址的数值。右值是不能对其进行赋值的表达式,也就是说,右值可以出现在赋值号的右边,但不能出现在赋值号的左边 - 变量作用域:在函数或一个代码块内部声明的变量,称为局部变量(只能被函数内部或者代码块内部的语句使用);在函数参数的定义中声明的变量,称为形式参数;在所有函数外部声明的变量,称为全局变量(在程序的整个生命周期内都是有效的)
- 数字可以加后缀:
212 // 合法的
215u // 合法的
0xFeeL // 合法的
078 // 非法的:8 不是八进制的数字
032UU // 非法的:不能重复后缀
85 // 十进制
0213 // 八进制
0x4b // 十六进制
30 // 整数
30u // 无符号整数
30l // 长整数
30ul // 无符号长整数
3.14159 // 合法的
314159E-5L // 合法的
510E // 非法的:不完整的指数
210f // 非法的:没有小数或指数
.e55 // 非法的:缺少整数或分数
- C++中的类型限定符
限定符 | 含义 |
---|---|
const | const 类型的对象在程序执行期间不能被修改改变。 |
volatile | 修饰符 volatile 告诉编译器不需要优化volatile声明的变量,让程序可以直接从内存中读取变量。对于一般的变量编译器会对变量进行优化,将内存中的变量值放在寄存器中以加快读写效率。 |
restrict | 由 restrict 修饰的指针是唯一一种访问它所指向的对象的方式。只有 C99 增加了新的类型限定符 restrict。 |
- volatile 关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改,比如:操作系统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问
- C++存储类:存储类定义 C++ 程序中变量/函数的范围(可见性)和生命周期。有:auto\register\static\extern\mutable\thread_local (C++11)
- auto:声明变量时根据初始化表达式自动推断该变量的类型、声明函数时函数返回值的占位符。 auto x = 100;
- register:用于定义存储在寄存器中而不是 RAM 中的局部变量,这意味着变量的最大尺寸等于寄存器的大小(通常是一个词),且不能对它应用一元的 ‘&’ 运算符(因为它没有内存位置)register int miles;
- static:指示编译器在程序的生命周期内保持局部变量的存在,而不需要在每次它进入和离开作用域时进行创建和销毁。因此,使用 static 修饰局部变量可以在函数调用之间保持局部变量的值。在 C++ 中,当 static 用在类数据成员上时,会导致仅有一个该成员的副本被类的所有对象共享。
- extern:用于提供一个全局变量的引用,全局变量对所有的程序文件都是可见的。当您使用 ‘extern’ 时,对于无法初始化的变量,会把变量名指向一个之前定义过的存储位置
- mutable:它允许对象的成员替代常量。也就是说,mutable 成员可以通过 const 成员函数修改
- thread_local 说明符声明的变量仅可在它在其上创建的线程上访问。 变量在创建线程时创建,并在销毁线程时销毁。 每个线程都有其自己的变量副本。thread_local 说明符可以与 static 或 extern 合并。可以将 thread_local 仅应用于数据声明和定义,thread_local 不能用于函数声明或定义】
- lambda函数与表达式:capture->return-type{body}
[](int x, int y) -> int { int z = x + y; return z + x; }
[this]() { this->someFunc(); }();
[] // 沒有定义任何变量。使用未定义变量会引发错误。
[x, &y] // x以传值方式传入(默认),y以引用方式传入。
[&] // 任何被使用到的外部变量都隐式地以引用方式加以引用。
[=] // 任何被使用到的外部变量都隐式地以传值方式加以引用。
[&, x] // x显式地以传值方式加以引用。其余变量以引用方式加以引用。
[=, &z] // z显式地以引用方式加以引用。其余变量以传值方式加以引用。
- 随机数:
// 设置种子
srand( (unsigned)time( NULL ) );
// 生成实际的随机数
j= rand();
cout <<"随机数: " << j << endl;
- 字符串:
- 字符串实际上是使用 null 字符 ‘\0’ 终止的一维字符数组。
- 会自动加上’\0’
- 引用(别名)和指针的区别:① 不存在空引用。引用必须连接到一块合法的内存 ② 一旦引用被初始化为一个对象,就不能被指向到另一个对象。指针可以在任何时候指向到另一个对象 ③ 引用必须在创建时被初始化。指针可以在任何时间被初始化。
- 将引用作为返回值:当函数返回一个引用时,则返回一个指向返回值的隐式指针。函数就可以放在赋值语句的左边。
double vals[] = {10.1, 12.6, 33.1, 24.1, 50.0};
double& setValues( int i ) {
return vals[i]; // 返回第 i 个元素的引用
}
setValues(1) = 20.23; // 改变第 2 个元素
- 要注意被引用的对象不能超出作用域。所以返回一个对局部变量的引用是不合法的,但是,可以返回一个对静态变量的引用
类
- 访问修饰符public、private、protected
- private 成员只能被本类成员(类内)和友元访问,不能被派生类访问;
- protected 成员可以被派生类访问。
- 访问修饰符在继承中的特点:
- public 继承:基类 public 成员,protected 成员,private 成员的访问属性在派生类中分别变成:public, protected, private
- protected 继承:基类 public 成员,protected 成员,private 成员的访问属性在派生类中分别变成:protected, protected, private
- private 继承:基类 public 成员,protected 成员,private 成员的访问属性在派生类中分别变成:private, private, private
- 友元函数:定义在类外部,但有权访问类的所有私有(private)成员和保护(protected)成员。尽管友元函数的原型有在类的定义中出现过,但是友元函数并不是成员函数。friend void printWidth( Box box );
- 类的静态成员:
- 静态成员变量:静态成员在类的所有对象中是共享的。如果不存在其他的初始化语句,在创建第一个对象时,所有的静态数据都会被初始化为零。我们不能把静态成员的初始化放置在类的定义中
- 不能把静态成员的初始化放置在类的定义中,但是可以在类的外部通过使用范围解析运算符 :: 来重新声明静态变量从而对它进行初始化
- 静态成员函数:静态成员函数即使在类对象不存在的情况下也能被调用,静态函数只要使用
类名加范围解析运算符 ::就可以访问
- 静态成员函数只能
访问静态成员数据
、其他静态成员函数
和类外部的其他函数
! - 静态成员函数有一个类范围,他们不能访问类的 this 指针。您可以使用静态成员函数来判断类的某些对象是否已被创建。
- 静态成员函数与普通成员函数的区别:
- 静态成员函数没有 this 指针,只能访问静态成员(包括静态成员变量和静态成员函数)
- 普通成员函数有 this 指针,可以访问类中的任意成员;而静态成员函数没有 this 指针
重载
- 运算符和函数重载
- 重载声明是指一个与之前已经在该作用域内声明过的函数或方法具有
相同名称
的声明,但是它们的参数列表和定义(实现)
不相同 - 选择最合适的重载函数或重载运算符的过程,称为重载决策
- 运算符重载:Box operator+(const Box&, const Box&);
- 运算重载符不可以改变语法结构。
- 运算重载符不可以改变操作数的个数。
- 运算重载符不可以改变优先级。
- 运算重载符不可以改变结合性。
- 不可重载运算符:
.:成员访问运算符
.*, ->*:成员指针访问运算符
:::域运算符
sizeof:长度运算符
?::条件运算符
#: 预处理符号
- 除此之外都可以重载
继承: 派生类 is a 基类
- 不希望类被继承可以用final
class Class_name final
{
...
};
- 继承允许我们依据另一个类来定义一个类,这使得创建和维护一个应用程序变得更容易。这样做,也达到了重用代码功能和提高执行效率的效果。
- 一个派生类继承了所有的基类方法,但下列情况除外:
- 基类的
构造函数、析构函数和拷贝构造函数
。 - 基类的
重载运算符
。 - 基类的
友元函数
。
- 基类的
- 虚拟继承
- 为了解决多重继承:
- 多重继承:在类D中两次出现类A中的变量和函数,类A建立了两个对象。为了解释内存,引入virtual继承!
- 时间上:增加引用寻址时间(就和虚函数一样)其实就是调整this指针以指向虚基类对象,只不过这个调整是运行时间接完成的
- 空间上:不需要基类A的多份拷贝,但多出多出一个指向基类子对象的指针(4字节)
class A
class B1:public virtual A;
class B2:public virtual A;
class D:public B1,public B2;
- 因为每个存在虚函数的类都要有一个4字节的指针指向自己的虚函数表,所以每种情况的类a所占的字节数应该是没有什么问题的,那么类b的字节数怎么算呢?看“第一种”和“第三种”情况采用的是虚继承,那么这时候就要有这样的一个指针vptr_b_a,这个指针叫虚类指针,也是四个字节;还要包括类a的字节数,所以类b的字节数就求出来了。而“第二种”和“第四种”情况则不包括vptr_b_a这个指针,这回应该木有问题了吧
多态
- 多态按字面的意思就是多种形态;
一个接口,多种方法
- C++支持两种多态性:
- 编译时多态性(静态多态):通过重载函数实现:先期联编 early binding(类内)
- 运行时多态性(动态多态):通过虚函数实现 ,滞后联编 late binding
- 当类之间存在层次结构,并且类之间是通过继承关联时,就会用到多态。
- C++ 多态意味着调用成员函数时,会根据调用函数的对象的类型来执行不同的函数。
- 如下例,可见输出不是我们希望的,因为
静态链接(静态多态)现象
:静态链接 - 函数调用在程序执行前就准备好了,也叫早绑定
现象:
#include <iostream>
using namespace std;
class Shape {
protected:
int width, height;
public:
Shape( int a=0, int b=0) {
width = a;
height = b;
}
int area() {
cout << "Parent class area :" <<endl;
return 0;
}
};
class Rectangle: public Shape{
public:
Rectangle( int a=0, int b=0):Shape(a, b) { }
int area () {
cout << "Rectangle class area :" <<endl;
return (width * height);
}
};
class Triangle: public Shape{
public:
Triangle( int a=0, int b=0):Shape(a, b) { }
int area () {
cout << "Triangle class area :" <<endl;
return (width * height / 2);
}
};
// 程序的主函数
int main( ) {
Shape *shape;
Rectangle rec(10,7);
Triangle tri(10,5);
// 存储矩形的地址
shape = &rec;
// 调用矩形的求面积函数 area
shape->area(); // 输出: Parent class area
// 存储三角形的地址
shape = &tri;
// 调用三角形的求面积函数 area
shape->area(); // 输出: Parent class area
return 0;
}
- 解决:在基类的函数
area()
声明前放置关键字virtual
,就行了!即虚函数!
虚函数
- 虚函数 是在基类中使用关键字 virtual 声明的函数
- 在派生类中重新定义基类中定义的虚函数时,会告诉编译器不要静态链接到该函数
- 我们想要的是在程序中任意点可以根据所调用的对象类型来选择调用的函数,这种操作被称为
动态链接,或后期绑定
,这个技术的核心是虚函数表 - 有时我们并不希望父类的某个函数在子类中被重写,在 C++11 及以后可以用关键字
final
来避免该函数再次被重写
class Base {
public:
virtual void func() {
cout<<"This is Base"<<endl;
}
};
class _Base:public Base {
public:
void func() final//正确,func在Base中是虚函数
{
cout<<"This is _Base"<<endl;
}
};
class __Base:public _Base {
/* public://不正确,func在_Base中已经不再是虚函数,不能再被重写
void func() {
cout<<"This is __Base"<<endl;
}*/
};
纯虚函数
- 如何选用虚函数和纯虚函数:
- 当基类中的某个成员方法,在大多数情形下都应该由子类提供个性化实现,但基类也可以提供缺省备选方案的时候,该方法应该设计为虚函数。
- 当基类中的某个成员方法,必须由子类提供个性化实现的时候,应该设计为纯虚函数
- 构造函数不能是虚函数,析构函数可以是虚函数且推荐最好设置为虚函数
- virtual int area() = 0; // = 0 告诉编译器,函数没有主体,上面的虚函数是纯虚函数。
总结
- 重载指的是函数具有的不同的参数列表,而函数名相同的函数。重载要求参数列表必须不同,比如参数的类型不同、参数的个数不同、参数的顺序不同。如果仅仅是函数的返回值不同是没办法重载的,因为重载要求参数列表必须不同。(发生在同一个类里)
- 覆盖是存在类中,子类重写从基类继承过来的函数。被重写的函数不能是static的。必须是virtual的。但是函数名、返回值、参数列表都必须和基类相同(发生在基类和子类)
- 重定义也叫做隐藏,子类重新定义父类中有相同名称的非虚函数 ( 参数列表可以不同 ) 。(发生在基类和子类)
- 在基类与子类函数名相同的前提下,根据参数是否相同、是否具有vritual关键字,可分为4种情况:
- 参数相同、有virtual关键字:多态重写;
- 参数相同、无virtual关键字:隐藏;与重写区分。
- 参数不同、有virtual关键字:隐藏;与重载区分。
- 参数不同、无virtual关键字:隐藏;与重载区分。
数据抽象
- 数据抽象是指,只向外界提供关键信息,并隐藏其后台的实现细节,即只表现必要的信息而不呈现细节
- 数据抽象是一种依赖于
接口和实现
分离的编程(设计)技术。 - C++ 类为数据抽象提供了可能。它们向外界提供了大量用于操作对象数据的公共方法,也就是说,外界实际上并不清楚类的内部实现。
- 数据抽象有两个重要的优势:
- 类的内部受到保护,不会因无意的用户级错误导致对象状态受损。
- 类实现可能随着时间的推移而发生变化,以便应对不断变化的需求,或者应对那些要求不改变用户级代码的错误报告。
接口(抽象类ABC)
- C++ 接口是使用
抽象类
来实现的,抽象类与数据抽象互不混淆,数据抽象是一个把实现细节与相关的数据分离开的概念。 - 如果类中至少有一个函数被声明为纯虚函数,则这个类就是抽象类。
- 如果一个 ABC 的子类需要被实例化,则必须实现每个虚函数,这也意味着 C++ 支持使用 ABC 声明接口
- 如果没有在派生类中重写纯虚函数,就尝试实例化该类的对象,会导致编译错误。
- 可用于实例化对象的类被称为
具体类
。
数据封装
- 封装是面向对象编程中的把
数据和操作数据的函数
绑定在一起的一个概念,这样能避免受到外界的干扰和误用,从而确保了安全。数据封装引申出了另一个重要的 OOP 概念,即数据隐藏。 - C++ 通过创建类来支持封装和数据隐藏(public、protected、private)
- 通常情况下,我们都会设置类成员状态为私有(private),除非我们真的需要将其暴露,这样才能保证良好的封装性。
- 这通常应用于数据成员,但它同样适用于所有成员,包括虚函数。
文件流
异常
动态内存
- 程序内存分为两个部分;
- 栈:在函数内部声明的所有变量都将占用栈内存。
- 堆:这是程序中未使用的内存,在程序运行时可用于
动态分配内存
double* pvalue = NULL;
if( !(pvalue = new double )) {
cout << "Error: out of memory." <<endl;
exit(1);
}
- malloc() 函数在 C 语言中就出现了,在 C++ 中仍然存在,但建议尽量不要使用 malloc() 函数。new 与 malloc() 函数相比,其主要的优点是,new
不只是分配了内存,它还创建了对象
- 删除内存:delete pvalue;
数组的动态内存分配
- 一维数组
// 动态分配,数组长度为 m
int *array=new int [m];
... ...
//释放内存
delete [] array;
- 二维数组
int **array
// 假定数组第一维长度为 m, 第二维长度为 n
// 动态分配空间
array = new int *[m];
for( int i=0; i<m; i++ )
{
array[i] = new int [n] ;
}
//释放
for( int i=0; i<m; i++ )
{
delete [] array[i];
}
delete [] array;
- 三维数组
int ***array;
// 假定数组第一维为 m, 第二维为 n, 第三维为h
// 动态分配空间
array = new int **[m];
for( int i=0; i<m; i++ )
{
array[i] = new int *[n];
for( int j=0; j<n; j++ )
{
array[i][j] = new int [h];
}
}
//释放
for( int i=0; i<m; i++ )
{
for( int j=0; j<n; j++ )
{
delete[] array[i][j];
}
delete[] array[i];
}
delete[] array;
对象的动态内存分配
Box* myBoxArray = new Box[4];
delete [] myBoxArray; // 删除数组
return 0;
问题
- delete 与 delete[] 区别:
- delete ptr – 代表用来释放内存,且只用来释放ptr指向的内存。
- delete[] rg – 用来释放rg指向的内存,!!还逐一调用数组中每个对象的 destructor!!
- 对于像 int/char/long/int*/struct 等等简单数据类型,由于对象没有 destructor,所以用 delete 和 delete [] 是一样的!但是如果是C++ 对象数组就不同了!
- new和malloc内部实现方式区别?
- new 的功能是在堆区新建一个对象,并返回该对象的指针。
- 所谓的【新建对象】的意思就是,将调用该类的构造函数,因为如果不构造的话,就不能称之为一个对象。
- 而 malloc 只是机械的分配一块内存,如果用 mallco 在堆区创建一个对象的话,是不会调用构造函数的。
- 同样的,用 delete 去释放一个堆区的对象,会调用该对象的析构函数。用 free 去释放一个堆区的对象,不会调用该对象的析构函数。
命名空间
模板 — 泛型编程
- 模板是泛型编程的基础,泛型编程即以一种独立于任何特定类型的方式编写代码。
- 模板是创建泛型
template <class T> // class可以换成typename
T Stack<T>::top () const
{
if (elems.empty()) {
throw out_of_range("Stack<>::top(): empty stack");
}
// 返回最后一个元素的副本
return elems.back();
}
- 为了避免 class 在这两个地方的使用可能给人带来混淆,所以引入了 typename 这个关键字
- typename 另外一个作用为:使用嵌套依赖类型(nested depended name)
- typename 的作用就是告诉 c++ 编译器,typename 后面的字符串为一个类型名称,而不是成员函数或者成员变量,这个时候如果前面没有 typename,编译器没有任何办法知道 T::LengthType 是一个类型还是一个成员名称(静态数据成员或者静态函数),所以编译不能够通过
class MyArray {
public:
typedef int LengthType;
.....
}
template<class T>
void MyMethod( T myarr ) {
typedef typename T::LengthType LengthType;
LengthType length = myarr.GetLength;
}
- 函数模板可以重载,只要它们的形参表不同即可
- 如果需要代码分离,即 template class 的声明、定义,以及 main 函数分属不同文件,main.cpp 文件中需要同时包含 .h 文件和 .cpp 文件。一般都是使用
.hpp
文件盛饭模板函数!
C++预处理
- #define / 参数宏 / 条件编译 /
参考
C++信号处理
- 信号是由操作系统传给进程的中断,会提早终止一个程序。
参考
C++ 多线程
- 两种类型的多任务处理:基于进程和基于线程。
- 基于进程的多任务处理是程序的并发执行。
- 基于线程的多任务处理是同一程序的片段的并发执行。
参考
C++ web编程
STL
Python
- 官方宣布,2020 年 1 月 1 日, 停止 Python 2 更新
- Python 3.6.3 中文手册
简介
- Python是一门解释型语言,因为无需编译和链接
- Python更紧凑
- 高级数据结构使你可以在一条语句中表达复杂的操作
- 语句组使用缩进代替开始和结束大括号来组织
- 变量或参数无需声明
- Python是可扩展的
- 交互模式下:
_
和Matlab中的ans
一样,就是上一个表达式的值 - 除了int和float,Pyhon还支持小数类型Decimal和分数类型Fraction,Python内建支持复数(3+5j)
- 字符串:可以用单引号也可以用双引号!,
\
用于转义引号! - 字符串文本能够分成多行。一种方法是使用三引号:
"""..."""
或者'''...'''
- 切片:
word[:2]: 'Py' / word[4:]: 'on' / word[-2:] 'on' / word[42:]: ''
- Python字符串不可以被更改
- 需要不同的字符串,应该创建一个新的!
- list:也支持
+
拼接;append()
,允许嵌套列表(创建一个包含其它列表的列表)
>>> a = ['a', 'b', 'c']
>>> n = [1, 2, 3]
>>> x = [a, n]
>>> x
[['a', 'b', 'c'], [1, 2, 3]]
>>> a, b = 0, 1
>>> while b < 1000:
... print(b, end=',')
... a, b = b, a+b
流程控制
if … elif … elif …
序列用于替代其它语言中的switch ... case
语句- Python 的 for 语句依据任意序列(链表或字符串)中的子项,按它们在序列中的顺序来进行迭代
- 在迭代过程中修改迭代序列不安全(只有在使用链表这样的可变序列时才会有这样的情况);使用切割标识
:
可以获得新的序列副本!
words = ['cat', 'window', 'defenestrate']
for w in words[:]: # Loop over a slice copy of the entire list.
... if len(w) > 6:
... words.insert(0, w)
...
>>> words
['defenestrate', 'cat', 'window', 'defenestrate']
- range()生成一个等差级数链表
range(0, 10, 3)
0, 3, 6, 9
>>> a = ['Mary', 'had', 'a', 'little', 'lamb']
>>> for i in range(len(a)):
... print(i, a[i])
...
0 Mary
1 had
2 a
3 little
4 lamb
- 这种场合也可以方便的使用 enumerate(),请参见 循环技巧
- break 和 continue 语句, 以及循环中的 else 子句
- break 语句和 C 中的类似,用于跳出最近的一级 for 或 while 循环
- continue 语句是从 C 中借鉴来的,它表示循环继续执行下一次迭代:
循环可以有一个 else子句
;它在循环迭代完整个列表(对于 for )或执行条件为 false (对于 while )时执行,但循环被 break 中止的情况下不会执行, 类似于try...else
>>> for n in range(2, 10):
... for x in range(2, n):
... if n % x == 0:
... print(n, 'equals', x, '*', n//x)
... break
... else:
... # loop fell through without finding a factor
... print(n, 'is a prime number')
...
2 is a prime number
3 is a prime number
4 equals 2 * 2
5 is a prime number
6 equals 2 * 3
7 is a prime number
8 equals 2 * 4
9 equals 3 * 3
- pass 语句什么也不做。它用于那些语法上必须要有什么语句,但程序什么也不做的场合:while结构,class结构;函数结构
- 函数体的第一行语句可以是可选的字符串文本,这个字符串是函数的文档字符串,或者称为 docstring。(更多关于 docstrings 的信息请参考 文档字符串) 有些工具通过 docstrings 自动生成在线的或可打印的文档,或者让用户通过代码交互浏览;
- 函数参数:
- 位置参数(必选参数)
- 默认参数:① 必选参数在前,默认参数在后 ② 重要警告: 默认值只被赋值一次。这使得当默认值是可变对象时会有所不同,比如列表、字典或者大多数类的实例。例如,下面的函数在后续调用过程中会累积(前面)传给它的参数:
def f(a, L=[]): L.append(a) return L print(f(1)) # [1] print(f(2)) # [1, 2] # 可以这样写: def f(a, L=None): if L is None: L = [] L.append(a) return L
- 可变参数:
*args
;listArray=[1,2,3] \n print(power(*listArray))
可变参数在函数调用时自动组装为一个tuple - 关键字参数:
**kw
, 这些关键字参数在函数内部自动组装为一个dict
#定义一个字典数据 dictArray = {'city': 'Beijing', 'job': 'Engineer'} #调用函数 person('Jack', 24, **dictArray ) #输出结果 name: Jack age: 24 other: {'city': 'Beijing', 'job': 'Engineer'}
- 命名关键字参数:命名关键字参数需要一个特殊分隔符
*
,而后面的参数被视为命名关键字参数。
def person(name, age, *, city, job): print(name, age, city, job) # 中已经有了一个可变参数,后面跟着的命名关键字参数就不再需要一个特殊分隔符*了 def person(name, age, *args, city, job): print(name, age, args, city, job)
- 参数组合:
必选参数–>默认参数–>可变参数–>命名关键字参数–>关键字参数
- 参数列表的分拆:当你要传递的参数已经是一个列表,但要调用的函数却接受分开一个个的参数值。这时候你要把已有的列表拆开来;tuple使用
*
分拆 + dict使用**
分拆!
# 元组分拆 args = [3, 6] list(range(*args)) # 字典分拆 d = {"voltage": "four million", "state": "bleedin' demised", "action": "VOOM"} parrot(**d)
- lambda函数:来自于函数式编程lisp的概念!
- 文档字符串:
""" ... """
;__doc__
- 函数注解:参数注解(Parameter annotations)是定义在参数名称的冒号后面,紧随着一个用来表示注解的值得表达式。返回注释(Return annotations)是定义在一个
->
后面,紧随着一个表达式,在:
与->
之间。下面的示例包含一个位置参数,一个关键字参数,和没有意义的返回值注释:>>> def f(ham: 42, eggs: int = 'spam') -> "Nothing to see here": ... print("Annotations:", f.__annotations__) ... print("Arguments:", ham, eggs) >>> f('wonderful') Annotations: {'eggs': <class 'int'>, 'return': 'Nothing to see here', 'ham': 42} Arguments: wonderful spam
- 编码风格PEP 8:
数据结构
列表 list
- list.append(x) / list.extend(L):
a[len(a):] = L
/ list.insert(i, x):将x插在i前
/ list.remove(x) / list.pop([i]):方法中i两边的方括号表示这个参数是可选的,而不是要求你输入一对方括号
/ list.clear() / list.index(x) / list.count(x) / list.sort() / list.reverse() / list.copy():浅拷贝 a[:]
- PS 浅拷贝和深拷贝的区别:(copy)拷贝父对象,不会拷贝对象的内部的子对象;(deepcopy)copy 模块的 deepcopy 方法,完全拷贝了父对象及其子对象
# 浅拷贝 >>>a = {1: [1,2,3]} >>> b = a.copy() >>> a, b ({1: [1, 2, 3]}, {1: [1, 2, 3]}) >>> a[1].append(4) >>> a, b ({1: [1, 2, 3, 4]}, {1: [1, 2, 3, 4]}) # 深拷贝 >>>import copy >>> c = copy.deepcopy(a) >>> a, c ({1: [1, 2, 3, 4]}, {1: [1, 2, 3, 4]}) >>> a[1].append(5) >>> a, c ({1: [1, 2, 3, 4, 5]}, {1: [1, 2, 3, 4]})
- 列表当堆栈:append(x) + pop()
- 列表当队列:① 相对来说从列表末尾添加和弹出很快;在头部插入和弹出很慢(因为,为了一个元素,要移动整个列表中的所有元素)② 实现队列,使用
collections.deque
,它为在首尾两端快速插入和删除而设计:pop() / popleft() ; append() / appendleft()
- 列表推到式:由包含一个表达式的括号组成,表达式后面跟随一个 for 子句,之后可以有零或多个 for 或 if 子句。结果是一个列表,由表达式依据其后面的 for 和 if 子句上下文计算而来的结果构成:
squares = list(map(lambda x: x**2, range(10)))
或者squares = [x**2 for x in range(10)]
>>> [(x, y) for x in [1,2,3] for y in [3,1,4] if x != y] [(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)]
- 嵌套的列表推导:越靠后的for,越属于外层循环!
>>> matrix = [ ... [1, 2, 3, 4], ... [5, 6, 7, 8], ... [9, 10, 11, 12], ... ] >>> [[row[i] for row in matrix] for i in range(4)] [[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]
- 上述例子:可用zip函数快速实现!
>>> list(zip(*matrix)) [(1, 5, 9), (2, 6, 10), (3, 7, 11), (4, 8, 12)]
del
语句:它不同于有返回值的 pop() 方法。语句 del 还可以从列表中删除切片或清空整个列表(我们以前介绍过一个方法是将空列表赋值给列表的切片)del a[2:4] del a[:] del a
元组 tuple
- 一个元组由数个逗号分隔的值组成
- 元组就像字符串, 不可变的
- 通常包含不同种类的元素并通过分拆或索引访问
- 元组封装:
# 元组封装 (tuple packing) t = 12345, 54321, 'hello!' >>> t (12345, 54321, 'hello!') v = ([1, 2, 3], [3, 2, 1]) >>> v ([1, 2, 3], [3, 2, 1]) singleton = 'hello', >>> singleton ('hello',)
- 序列拆分,序列拆封要求左侧的变量数目与序列的元素个数相同;PS:可变参数(multiple assignment )其实只是元组封装和序列拆封的一个结合
>>> x, y, z = t
集合 set
- 一个无序不重复元素的集。基本功能包括关系测试和消除重复元素
- 集合对象支持关系有:union(联合)
|
,intersection(交)&
,difference(差)-
和 sysmmetric difference(对称差集)^
等数学运算 - 集合推导式语法
{}
>>> a = {x for x in 'abracadabra' if x not in 'abc'} >>> a {'r', 'd'}
字典 dict
- 序列是以连续的整数为索引,与此不同的是,字典以 关键字 为索引,关键字可以是任意不可变类型(如数值或字符串)
- 无序的键-值对 (key:value 对)集合,键必须是互不相同的(在同一个字典之内)
{} del list(d.keys()) sorted(d.keys()) in
- 定义
>>> dict([('sape', 4139), ('guido', 4127), ('jack', 4098)]) {'sape': 4139, 'jack': 4098, 'guido': 4127} >>> dict(sape=4139, guido=4127, jack=4098) {'sape': 4139, 'jack': 4098, 'guido': 4127}
- 字典推导式
>>> {x: x**2 for x in (2, 4, 6)} {2: 4, 4: 16, 6: 36}
序列结构在循环结构中
- 在字典中循环时,关键字和对应的值可以使用
items()
方法同时解读出来: >>> knights = {'gallahad': 'the pure', 'robin': 'the brave'} >>> for k, v in knights.items(): ... print(k, v) ... gallahad the pure robin the brave
- 在序列中循环时,索引位置和对应值可以使用
enumerate()
函数同时得到:
>>> for i, v in enumerate(['tic', 'tac', 'toe']):
... print(i, v)
...
0 tic
1 tac
2 toe
- 同时循环两个或更多的序列,可以使用 zip() 整体打包:
>>> questions = ['name', 'quest', 'favorite color']
>>> answers = ['lancelot', 'the holy grail', 'blue']
>>> for q, a in zip(questions, answers):
... print('What is your {0}? It is {1}.'.format(q, a))
...
What is your name? It is lancelot.
What is your quest? It is the holy grail.
What is your favorite color? It is blue.
- 逆向循环序列:
>>> for i in reversed(range(1, 10, 2)):
... print(i)
...
9
7
5
3
1
- 排序后的顺序循环序列的话,使用 sorted() 函数,它不改动原序列,而是生成一个新的已排序的序列:
>>> basket = ['apple', 'orange', 'apple', 'pear', 'orange', 'banana']
>>> for f in sorted(set(basket)):
... print(f)
...
apple
banana
orange
pear
- 要在循环内部修改正在遍历的序列(例如复制某些元素),建议您首先制作副本:
>>> words = ['cat', 'window', 'defenestrate'] >>> for w in words[:]: # Loop over a slice copy of the entire list. ... if len(w) > 6: ... words.insert(0, w) ... >>> words ['defenestrate', 'cat', 'window', 'defenestrate']
条件控制
a < b == c
- 优先级:
or/and < not <
比较操作:A and not B or C
等于(A and (notB)) or C
- 逻辑操作符 and 和 or 也称作短路操作符
>>> string1, string2, string3 = '', 'Trondheim', 'Hammer Dance' >>> non_null = string1 or string2 or string3 >>> non_null 'Trondheim'
序列间比较
- 首先比较前两个元素,如果不同,就决定了比较的结果;如果相同,就比较后两个元素,依此类推,直到所有序列都完成比较。如果两个元素本身就是同样类 型的序列,就递归字典序比较。如果两个序列的所有子项都相等,就认为序列相等。如果一个序列是另一个序列的初始子序列,较短的一个序列就小于另一个。字符 串的字典序按照单字符的 ASCII 顺序
模块
- 模块名:
__name__
- import from…import…
- 模块的搜索路径:sys.path PYTHONPATH
- “编译的”Py文件,为了加快加载模块的速度,Python 会在 pycache 目录下以 module.version.pyc 名字缓存每个模块编译后的版本,这里的版本编制了编译后文件的格式。它通常会包含 Python 的版本号。例如,在 CPython 3.3 版中,spam.py 编译后的版本将缓存为 pycache/spam.cpython-33.pyc。这种命名约定允许由不同发布和不同版本的 Python 编译的模块同时存在
- 为了减少一个编译模块的大小,你可以在 Python 命令行中使用
-O
或者-OO
。-O 参数删除了断言语句,-OO 参数删除了断言语句和 doc 字符串。“优化的” 模块有一个 .pyo 后缀而不是 .pyc 后缀 - 来自 .pyc 文件或 .pyo 文件中的程序不会比来自 .py 文件的运行更快;.pyc 或 .pyo 文件只是在它们加载的时候更快一些。
- compileall 模块可以为指定目录中的所有模块创建 .pyc 文件(或者使用 -O 参数创建 .pyo 文件)
- 在 PEP 3147 中有很多关这一部分内容的细节,并且包含了一个决策流程
- Python的标准模块:发布有独立的文档,名为 Python 库参考手册(此后称其为“库参考手册”)
- 内置函数 dir() 用于按模块名搜索模块定义,它返回一个字符串类型的存储列表
- Package: A.B;from package import item
- 从
*
导入包:from sound.effects import *,如果包中的 init.py 代码定义了一个名为 all 的列表,就会按照列表中给出的模块名进行导入 - Package内引用:
- from . import echo:当前路径下的模块echo
- from … import formats:上一级路径下的模块formats
- from …filters import equalizer:上一级的filters下的equalizer模块
- 多重目录中的包:包支持一个更为特殊的特性, path。 在包的 init.py 文件代码执行之前,该变量初始化一个目录名列表。该变量可以修改,它作用于包中的子包和模块的搜索功能。这个功能可以用于扩展包中的模块集,不过它不常用
输入输出
- 表达式语句 和 print() 函数(第三种访求是使用文件对象的 write() 方法,标准文件输出可以参考 sys.stdout
- str.format()
- 将某一类型的变量或者常量转换为字符串对象通常有两种方法,函数 str() 用于将值转化为适于人阅读的形式,而 repr() 转化为供解释器读取的形式
- str.rjust() / str.ljust() / str.center()
- 在字段后的 ‘:’ 后面加一个整数会限定该字段的最小宽度,这在美化表格时很有用:print(’{0:10} ==> {1:10d}’.format(name, phone))
- 文件读写:函数 open() 返回 文件对象,通常的用法需要两个参数:open(filename, mode)
- f.read(size) / f.readline() 从文件中读取单独一行 / f.write(‘This is a test\n’)
- f.tell() 返回一个整数,代表文件对象在文件中的指针位置,该数值计量了自文件开头到指针处的比特数
- 需要改变文件对象指针话话,使用 f.seek(offset,from_what)。指针在该操作中从指定的引用位置移动 offset 比特,引用位置由 from_what 参数指定
- f.close() 方法就可以关闭它并释放其占用的所有系统资源
- 用关键字 with 处理文件对象是个好习惯。它的先进之处在于文件用完后会自动关闭,就算发生异常也没关系。它是 try-finally 块的简写
- json 存储结构化数据:dumps() 函数的另外一个变体 dump(),直接将对象序列化到一个文件;x = json.load(f);
错误和异常
类 Class
类简介
- Python 的类并没有在用户和定义之间设立绝对的屏障,而是依赖于用户不去“强行闯入定义”的优雅. 是C++和Modula-3的混合!
- 类继承机制允许
多重继承
,派生类可以覆盖(override)基类
中的任何方法或类,可以使用相同的方法名称调用基类的方法。对象可以包含任意数量私有数据 - 用 C++ 术语来讲,所有的类成员(包括数据成员)都是公有( public )的(其它情况见下文 私有变量),所有的成员函数都是虚( virtual )的。
- 用 Modula-3 的术语来讲,在成员方法中没有简便的方式引用对象的成员:方法函数在定义时需要以引用的对象做为第一个参数,调用时则会隐式引用对象。像在 Smalltalk 中一个,类也是对象。这就提供了导入和重命名语义。
- 不像 C++ 和 Modula-3 中那样,大多数带有特殊语法的内置操作符(算法运算符、下标等)都可以针对类的需要重新定义
Python中的作用域和命名空间
- 包含局部命名的使用域在最里面,首先被搜索;其次搜索的是中层的作用域,这里包含了同级的函数;
- 最后搜索最外面的作用域,它包含内置命名。
- 首先搜索最内层的作用域,它包含局部命名任意函数包含的作用域,是内层嵌套作用域搜索起点,包含非局部,但是也非全局的命名
- 接下来的作用域包含当前模块的全局命名
- 最外层的作用域(最后搜索)是包含内置命名的命名空间
- global 语句用以指明某个特定的变量为全局作用域,并重新绑定它。nonlocal 语句用以指明某个特定的变量为封闭作用域,并重新绑定它。
类
- 类对象支持:属性引用(直接点号引用出类的数据和方法,可以赋值修改)和实例化(函数式实例化X = MyClass())
- 说明:① 同名的数据类型会覆盖同名的方法属性(使用命名规则避免) ② 数据属性可以被方法引用(Python中不可能强制隐藏数据,一切决定于用户自己的约定)
- 方法的第一个参数被命名为 self。这仅仅是一个约定:对 Python 而言,名称 self 绝对没有任何特殊含义(都是约定)
类中的成员函数
- classmethod 修饰符对应的函数不需要实例化,不需要 self 参数,但第一个参数需要是表示自身类的 cls 参数,可以来调用类的属性,类的方法,实例化对象等
class A(object):
# 属性默认为类属性(可以给直接被类本身调用)
num = "类属性"
# 实例化方法(必须实例化类之后才能被调用)
def func1(self): # self : 表示实例化类后的地址id
print("func1")
print(self)
# 类方法(不需要实例化类就可以被类本身调用)
@classmethod
def func2(cls): # cls : 表示没用被实例化的类本身
print("func2")
print(cls)
print(cls.num)
cls().func1()
# 不传递传递默认self参数的方法(该方法也是可以直接被类调用的,但是这样做不标准)
def func3():
print("func3")
print(A.num) # 属性是可以直接用类本身调用的
# A.func1() 这样调用是会报错:因为func1()调用时需要默认传递实例化类后的地址id参数,如果不实例化类是无法调用的
A.func2()
A.func3()
继承
- class DerivedClassName(modname.BaseClassName): (得是在同一个作用域!)
- 约定:① 派生类定义的执行过程和基类是一样的。构造派生类对象时,就记住了基类。这在解析属性引用的时候尤其有用:如果在类中找不到请求调用的属性,就搜索基类。如果基类是由别的类派生而来,这个规则会递归的应用上去 ② 派生类的实例化没有什么特殊之处 ③ 派生类可能会覆盖其基类的方法(因为方法调用同一个对象中的其它方法时没有特权,基类的方法调用同一个基类的方法时,可能实际上最终调用了派生类中的覆盖方法),从C++角度来说,Python 中的所有方法本质上都是
虚
方法 ④ 派生类中的覆盖方法可能是想要扩充而不是简单的替代基类中的重名方法,一种简单的调用基类方法的方法:只要调用BaseClassName.methodname(self, arguments)
- Python中有两个用于继承的函数!
- 函数
isinstance()
用于检查实例类型: isinstance(obj, int) 只有在 obj.class 是 int 或其它从 int 继承的类型 - 函数
issubclass()
用于检查类继承: issubclass(bool, int) 为 True,因为 bool 是 int 的子类。
- 函数
多继承
- class DerivedClassName(Base1, Base2, Base3):
- 在大多数情况下,在最简单的情况下,搜索属性从父类继承的深度优先,左到右,即 Base1 > Base3
super() 可以动态的改变解析顺序
。这个方式可见于其它的一些多继承语言,类似 call-next-method,比单继承语言中的 super 更强大- 动态调整顺序十分必要的,因为所有的多继承会有一到多个菱形关系(指有至少一个祖先类可以从子类经由多个继承路径到达)。例如,所有的 new-style 类继承自 object ,所以任意的多继承总是会有多于一条继承路径到达 object
私有变量
- 只能从对像内部访问的“私有”实例变量,在 Python 中不存在
- 也有一个变通的访问用于大多数 Python 代码:以一个下划线开头的命名(例如 _spam )会被处理为 API 的非公开部分(无论它是一个函数、方法或数据成员)。它会被视为一个实现细节,无需公开。
- 因为有一个正当的类私有成员用途(即避免子类里定义的命名与之冲突),Python 提供了对这种结构的有限支持,称为 name mangling (命名编码) 。任何形如 __spam 的标识(前面至少两个下划线,后面至多一个),被替代为 _classname__spam ,去掉前导下划线的 classname 即当前的类名。此语法不关注标识的位置,只要求在类定义内
空类的作用
- 类似于C中的struct结构,将数据和方法进行绑定!
class Employee:
pass
john = Employee() # Create an empty employee record
# Fill the fields of the record
john.name = 'John Doe'
john.dept = 'computer lab'
john.salary = 1000
- m.self 是一个实例方法所属的对象,而 m.func 是这个方法对应的函数对象
异常也是类
迭代器
- 内置迭代器工具,比如 itertools 函数返回的都是迭代器对象:count无限迭代器;cycle 无限迭代器,从一个有限序列中生成无限序列;islice 控制无限迭代器输出的方式
- 迭代器的用法在 Python 中普遍而且统一。在后台, for 语句在容器对象中调用 iter() 。该函数返回一个定义了 next() 方法的迭代器对象,它在容器中逐一访问元素。
- 没有后续的元素时, next() 抛出一个 StopIteration 异常通知 for 语句循环结束
- 用内建的 next() 函数调用 next() 方法;
>>> s = 'abc' >>> it = iter(s) >>> next(it)
生成器
Generator
是创建迭代器的简单而强大的工具。- 写起来就像是正规的函数,需要返回数据的时候使用
yield
语句。 生成器和函数的区别
:① 难理解的就是generator和函数的执行流程不一样。② 函数是顺序执行,遇到return语句或者最后一行函数语句就返回。③ 变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。- yield:它首先是个return,再把它看做一个是生成器(generator)的一部分(带yield的函数才是真正的迭代器)
- 每次 next() 被调用时,生成器回复它脱离的位置(它记忆语句最后一次执行的位置和所有的数据值)
def reverse(data): for index in range(len(data)-1, -1, -1): yield data[index] >>> for char in reverse('golf'): ... print(char) ... f l o g
- 生成器表达式:这些表达式是为函数调用生成器而设计的。生成器表达式比完整的生成器定义更简洁,但是没有那么多变,而且通常比等价的链表推导式更容易记:生成器表达式来自于迭代和列表解析的组合,生成器表达式和列表解析类似,但是他使用
()
而不是[]
括起来的g = (x * x for x in range(10)) for n in g: print(n,end=";")
- 用for循环调用generator时,发现拿不到generator的return语句的返回值。如果想要拿到返回值,必须捕获StopIteration错误,返回值包含在StopIteration的value中:
g = fib(6)
while True:
try:
x = next(g)
print('g:', x)
except StopIteration as e:
print('Generator return value:', e.value)
break
-
生成器在Python中是一个非常强大的编程结构,可以用更少地中间变量写流式代码,此外,相比其它容器对象它更能节省内存和CPU,当然它可以用更少的代码来实现相似的功能,但凡看到:
def something(): result= [] for ... in ...: result.append(x) return result
-
可以用下面代码替代:
def iter_something(): result = [] for ... in ...: yield x
-
python生成器有两个主要方法,next()和send()在一定意义上作用是相似的,区别是send()可以传递yield表达式的值进去,而next()不能传递特定的值,只能传递None进去!
-
生成器的并行
装饰器 decorator
简介
- 不添加额外代码的情况下,为函数或者类增加属性!
- 本质类似于 嵌套概念!
一 函数装饰函数
def wrapFun(func):
def inner(a, b):
print('function name:', func.__name__)
r = func(a, b)
return r
return inner
@wrapFun
def myadd(a, b):
return a + b
print(myadd(2, 3))
- 输出:
function name: myadd
5
二 函数装饰类
def wrapClass(cls):
def inner(a):
print('class name:', cls.__name__)
return cls(a)
return inner
@wrapClass
class Foo():
def __init__(self, a):
self.a = a
def fun(self):
print('self.a =', self.a)
m = Foo('xiemanR')
m.fun()
- 输出:
class name: Foo
self.a = xiemanR
三 类装饰函数
class ShowFunName():
def __init__(self, func):
self._func = func
def __call__(self, a):
print('function name:', self._func.__name__)
return self._func(a)
@ShowFunName
def Bar(a):
return a
print(Bar('xiemanR'))
- 输出:
function name: Bar
xiemanR
四 类装饰类
class ShowClassName(object):
def __init__(self, cls):
self._cls = cls
def __call__(self, a):
print('class name:', self._cls.__name__)
return self._cls(a)
@ShowClassName
class Foobar(object):
def __init__(self, a):
self.value = a
def fun(self):
print(self.value)
a = Foobar('xiemanR')
a.fun()
- 输出:
class name: Foobar
xiemanR
常见的类装饰器
- python的 @staticmethod,@classmethod和@property的区别
- 类的一般来说需要实例化后才能调用。但是使用了这前面两个装饰器,就可以不用实例化就可以直接调用类了。直接 类名.方法名() 来调用。
- @staticmethod 不需要表示自身对象的self和自身类的cls参数,就和使用普通的函数一样。
- @classmethod 不需要self参数,但是第一个cls参数需要表示自身类的cls参数。
- @property简而言之就是可以在调用的时候不用加();类里面没有__init__ 的话,@property会失效。
应用序列函数
- map / filter / reduce
map
- map函数会根据提供的函数对指定序列做映射
- map(function, sequence[, sequence, …]) -> list
- lambda函数
map(lambda x: x ** 2, [1, 2, 3, 4, 5])
返回结果为:
[1, 4, 9, 16, 25]
```
- **单参数例子**
```
>>> def add100(x):
... return x+100
...
>>> hh = [11,22,33]
>>> map(add100,hh)
[111, 122, 133]
- list参数
>>> def abc(a, b, c):
... return a*10000 + b*100 + c
...
>>> list1 = [11,22,33]
>>> list2 = [44,55,66]
>>> list3 = [77,88,99]
>>> map(abc,list1,list2,list3)
[114477, 225588, 336699]
- function = None
>>> list1 = [11,22,33]
>>> map(None,list1)
[11, 22, 33]
>>> list1 = [11,22,33]
>>> list2 = [44,55,66]
>>> list3 = [77,88,99]
>>> map(None,list1,list2,list3)
[(11, 44, 77), (22, 55, 88), (33, 66, 99)]
filter
- filter函数会对指定序列执行过滤操作。
- filter函数的定义:filter(function or None, sequence) -> list, tuple, or string (function参数为None,返回结果和sequence参数相同)
- function是一个谓词函数,接受一个参数,返回布尔值True或False
- filter函数会对序列参数sequence中的每个元素调用function函数,最后返回的结果包含调用结果为True的元素。返回值的类型和参数sequence的类型相同
def is_even(x):
return x & 1 != 0
filter(is_even, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
返回结果为:
[1, 3, 5, 7, 9]
reduce
- reduce函数,reduce函数会对参数序列中元素进行累积。
- py3以后使用,必须导入 from functools import reduce
- reduce函数的定义:reduce(function, sequence[, initial]) -> value
- function参数是一个有两个参数的函数,reduce依次从sequence中取一个元素,和上一次调用function的结果做参数再次调用function。
- 第一次调用function时,如果提供initial参数,会以sequence中的第一个元素和initial作为参数调用function,否则会以序列sequence中的前两个元素做参数调用function。
def myadd(x,y):
return x+y
sum=reduce(myadd,(1,2,3,4,5,6,7))
print(sum)
#结果就是输出1+2+3+4+5+6+7的结果即28
reduce(lambda x, y: x + y, [2, 3, 4, 5, 6], 1)
结果为21( (((((1+2)+3)+4)+5)+6) )
reduce(lambda x, y: x + y, [2, 3, 4, 5, 6])
结果为20
Python中的标准库
- os / glob / sys / re / math / urllib / datetime / (zlib/gzip/bz2/lzma/zipfile/tarfile) / timeit / (doctest/unittest) /
- (xmlrpc.client 和 xmlrpc.server) / email / xml.dom / xml.sax / gettext / locale / codecs
- reprlib / pprint / textwrap / locale / struct / threading / logging / weakref // array / collections.deque / bisect / heapq /
- decimal