0%

认识 Python

人生苦短,我用 Python —— Life is short, you need Python

目标

  • Python 的起源
  • 为什么要用 Python?
  • Python 的特点
  • Python 的优缺点
阅读全文 »

函数基础

目标

  • 函数的快速体验
  • 函数的基本使用
  • 函数的参数
  • 函数的返回值
  • 函数的嵌套调用
  • 在模块中定义函数

01. 函数的快速体验

1.1 快速体验

  • 所谓函数,就是把 具有独立功能的代码块 组织为一个小模块,在需要的时候 调用
  • 函数的使用包含两个步骤:
    1. 定义函数 —— 封装 独立的功能
    2. 调用函数 —— 享受 封装 的成果
  • 函数的作用,在开发程序时,使用函数可以提高编写的效率以及代码的 重用

演练步骤

  1. 新建 04_函数 项目
  2. 复制之前完成的 乘法表 文件
  3. 修改文件,增加函数定义 multiple_table():
  4. 新建另外一个文件,使用 import 导入并且调用函数

02. 函数基本使用

2.1 函数的定义

定义函数的格式如下:

1
2
3
4
def 函数名():

函数封装的代码
……
  1. def 是英文 define 的缩写
  2. 函数名称 应该能够表达 函数封装代码 的功能,方便后续的调用
  3. 函数名称 的命名应该 符合 标识符的命名规则
    • 可以由 字母下划线数字 组成
    • 不能以数字开头
阅读全文 »

1、继承

基类的构造函数和析构函数不可以被继承。

派生类继承至基类(父类继承至子类),派生类对于基类成员的继承是没有选择的,不能选择接收或者舍弃基类中的成员。

2、类的层次结构

通过继承可以形成类的层次结构,比如:

1
2
3
4
5
class A {......};

class B:public A {......};

class C:public B{.......};

即A为顶层类,不存在不可访问成员,C作为底层类。

3、基类成员按操作分类

public成员:公有成员,类内、类外、派生类都可以使用。

protect成员:受保护成员,类内、派生类可以使用。

private成员:私有成员,类内可以使用。

不可访问成员:不可访问。

阅读全文 »

头文件

头文件的所有内容,都必须包含在

#ifndef {Filename}
#define {Filename}

//{Content of head file}

#endif

这样才能保证头文件被多个其他文件引用(include)时,内部的数据不会被多次定义而造成错误

inline限定符

在头文件中,可以对函数用inline限定符来告知编译器,这段函数非常的简单,可以直接嵌入到调用定义之处。

当然inline的函数并不一定会被编译器作为inline来实现,如果函数过于复杂,编译器也会拒绝inline。

因此简单说来,代码最好短到只有3-5行的才作为inline。有循环,分支,递归的函数都不要用做inline。

对于在类定义内定义实现的函数,编译器自动当做有inline请求(也是不一定inline的)。因此在下边,我把带有inline限定符的函数成员和写在类定义体内的函数成员统称为“要inline的函数成员”

非模板类型

全局类型

就像前面笼统的话讲的:申明写在.h文件。

对于函数来讲,没有实现体的函数,就相当于是申明;而对于数据类型(包括基本类型和自定义类型)来说,其申明就需要用extern来修饰。

然后在.cpp文件里定义、实现或初始化这些全局函数和全局变量。

不过导师一直反复强调:不许使用全局函数和全局变量。用了之后造成的后果,目前就是交上去的作业项目会扣分。当然不能用自有不能用的理由以及解决方案,不过不在目前的讨论范围内。

阅读全文 »

1、重载函数:

以函数形参来区分不同函数,而不能以返回值来区分

无法以顶层const形参来定义重载函数

即:

1
2
3
4
void func1(int x)
void func1(const int x)//函数func1()重复声明 函数内部不能改变变量的值
void func2(int *x)
void fun2(int *const x)//函数func2()重复声明 指针指向不变

重载和默认参数会出现调用的二义性:

1
2
3
int add(int x,int y=1)
void add(int x)
add(10)

2、构造函数:

初始化表:==防止const类成员不可初始化

类中定义了一个构造函数之后,C++不再提供默认的构造函数,即如果只定义了含参构造函数,在创建不带参数的对象时会报错,所以在之后的构造函数中最好自定义无参和含参构造函数。

8、一个类A中含有另外一个类B的对象b时,称那个对象b为该类的成员对象,并且在构造函数中先执行成员对象b的构造函数,再执行该类A的构造函数(如果是含参构造函数,则该类A中的构造函数需要使用初始化标的形式对成员对象b进行初始化),而析构函数恰好相反。(在创建对象时调用构造函数)

即:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Data
{
public:
Data(int y,int m,int d):m_year(y),m(m_month,m_day);
private:
int m_year,m_month,m_nday;
};
class Student
{
public:
Student(string name.int y,int m,int d):Name(name),m_birthday(y,m,d);
private:
Date m_birthday;
string Name;

};

3、C++中的特殊定义

左值和右值

简言之,左值指的是变量/变量表达式(指向内存位置的表达式eg:1/2a+1/3a),右值指的是数值/数值表达式(存储在内存中某些地址的数值eg:2*3)。左值可以出现在赋值号的左边或者右边,右值只能出现在赋值号右边。

常量定义(字符常量和常变量)

c/c++中常量(通常定义成大写形式)有两种定义方式:

1、使用#define 预处理器

eg:#define PI 3.14

2、使用const关键字

eg:const int LENGTH=10;

修饰符类型

1
2
3
4
5
6
7
1signed

2unsigned

3long

4、short

修饰符 signed、unsigned、long 和 short 可应用于整型,signedunsigned 可应用于字符型,long 可应用于双精度型。

修饰符 signedunsigned 也可以作为 longshort 修饰符的前缀。例如:unsigned long int

C++ 允许使用速记符号来声明无符号短整数无符号长整数。您可以不写 int,只写单词 unsigned、shortunsigned、long,int 是隐含的。

类型限定符

限定符 含义
const const 类型的对象在程序执行期间不能被修改改变。
volatile 修饰符 volatile 告诉编译器不需要优化volatile声明的变量,让程序可以直接从内存中读取变量。对于一般的变量编译器会对变量进行优化,将内存中的变量值放在寄存器中以加快读写效率。
restrict restrict 修饰的指针是唯一一种访问它所指向的对象的方式。只有 C99 增加了新的类型限定符 restrict。

存储类型

1、auto 根据初始化表达式自动推断被声明的变量的类型

2、register 用于定义存储在寄存器中而不是 RAM 中的局部变量,变量的最大尺寸等于寄存器的大小

3、static static 有三种作用。第一种是保持局部变量的持久,static变量值初始化一次,声明周期为整个源程序。编译器在程序的生命周期内保持局部变量的存在,而不需要在每次它进入和离开作用域时进行创建和销毁。因此,使用 static 修饰局部变量可以在函数调用之间保持局部变量的值。第二种是限制全局变量的作用域,static 修饰全局变量时,会使变量的作用域限制在声明它的文件内。第三种是使类的成员变量和成员函数独立于类的对象而存在,使他们属于类,而不是对象,不需要实例化就可以分配内存。static成员存放在程序的全局(静态)存储中,不算在类大小中。

4、extern 用于提供一个全局变量的引用,全局变量对所有的程序文件都是可见的。

5、mutable mutable 是为了突破 const 的限制而设置的。可以用来修饰一个类的成员变量。被 mutable 修饰的变量,将永远处于可变的状态,即使是 const 函数中也可以改变这个变量的值。

6、thread_local (C++11) 仅应用于数据声明和定义,thread_local 不能用于函数声明或定义。其声明的变量在创建线程时创建,并在销毁线程时销毁。每个使用该变量的线程都有其自己的变量副本。

4、特殊成员变量和特殊成员函数在类中的使用

static 静态数据成员

为所有对象共有,不为某一个对象特有,即为整个类的公共数据,存储在公共内存中,并且在类外进行定义(分配空间并赋值)。

static 静态成员函数

无隐含的this指针,无法直接访问非静态数据成员,只能通过参数传递(类对象引用参数)进行访问,与静态数据成员一样,也为类所有,所以一般用于访问静态数据成员,并进行操作。

const 常数据成员

可以防止随意修改数据,即无法赋值,只能通过初始化表进行初始化。

const 常成员函数

常成员函数可以访问类中的const数据和非const数据,但是不能修改它们的值(可读不可写)。

常成员函数不可以调用非const成员函数。

非const成员函数可以读取const数据成员,但不可以修改(可读不可写)。

常成员函数中的this指针为常量,以此防止对数据成员的意外修改。

常对象只能调用其常成员函数。

5、头文件.h和源文件.cpp

头文件:通常放置数据的定义和声明。

写类的声明(包括类里面的成员和方法的声明)、函数原型、#define常数等,但一般来说不写出具体的实现。

在写头文件时需要注意,在开头和结尾处必须按照如下样式加上预编译语句(如下):

C++中头文件(.h)和源文件(.cpp)都应该写些什么#ifndef CIRCLE_H
C++中头文件(.h)和源文件(.cpp)都应该写些什么#define CIRCLE_H
C++中头文件(.h)和源文件(.cpp)都应该写些什么
C++中头文件(.h)和源文件(.cpp)都应该写些什么//你的代码写在这里
C++中头文件(.h)和源文件(.cpp)都应该写些什么
C++中头文件(.h)和源文件(.cpp)都应该写些什么#endif

这样做是为了防止重复编译,不这样做就有可能出错。

至于CIRCLE_H这个名字实际上是无所谓的,你叫什么都行,只要符合规范都行。原则上来说,非常建议把它写成这种形式,因为比较容易和头文件的名字对应。

源文件:通过放置函数的具体实现。

源文件主要写实现头文件中已经声明的那些函数的具体代码。需要注意的是,开头必须#include一下实现的头文件,以及要用到的头文件。那么当你需要用到自己写的头文件中的类时,只需要#include进来就行了。

下面举个最简单的例子来描述一下,咱就求个圆面积。

第1步,建立一个空工程(以在VS2003环境下为例)。

第2步,在头文件的文件夹里新建一个名为Circle.h的头文件,它的内容如下:

C++中头文件(.h)和源文件(.cpp)都应该写些什么#ifndef CIRCLE_H
C++中头文件(.h)和源文件(.cpp)都应该写些什么#define CIRCLE_H
C++中头文件(.h)和源文件(.cpp)都应该写些什么
C++中头文件(.h)和源文件(.cpp)都应该写些什么class Circle
C++中头文件(.h)和源文件(.cpp)都应该写些什么{
C++中头文件(.h)和源文件(.cpp)都应该写些什么private:
C++中头文件(.h)和源文件(.cpp)都应该写些什么 double r;//半径
C++中头文件(.h)和源文件(.cpp)都应该写些什么public:
C++中头文件(.h)和源文件(.cpp)都应该写些什么 Circle();//构造函数
C++中头文件(.h)和源文件(.cpp)都应该写些什么 Circle(double R);//构造函数
C++中头文件(.h)和源文件(.cpp)都应该写些什么 double Area();//求面积函数
C++中头文件(.h)和源文件(.cpp)都应该写些什么};
C++中头文件(.h)和源文件(.cpp)都应该写些什么
C++中头文件(.h)和源文件(.cpp)都应该写些什么#endif

注意到开头结尾的预编译语句。在头文件里,并不写出函数的具体实现。

第3步,要给出Circle类的具体实现,因此,在源文件夹里新建一个Circle.cpp的文件,它的内容如下:

C++中头文件(.h)和源文件(.cpp)都应该写些什么#include “Circle.h”
C++中头文件(.h)和源文件(.cpp)都应该写些什么
C++中头文件(.h)和源文件(.cpp)都应该写些什么)Circle::Circle()

C++中头文件(.h)和源文件(.cpp)都应该写些什么
C++中头文件(.h)和源文件(.cpp)都应该写些什么Circle::Circle(double R)
C++中头文件(.h)和源文件(.cpp)都应该写些什么{
C++中头文件(.h)和源文件(.cpp)都应该写些什么 this->r=R;
C++中头文件(.h)和源文件(.cpp)都应该写些什么}
C++中头文件(.h)和源文件(.cpp)都应该写些什么
C++中头文件(.h)和源文件(.cpp)都应该写些什么double Circle:: Area()
C++中头文件(.h)和源文件(.cpp)都应该写些什么{
C++中头文件(.h)和源文件(.cpp)都应该写些什么 return 3.14rr;
C++中头文件(.h)和源文件(.cpp)都应该写些什么}

需要注意的是:开头处包含了Circle.h,事实上,只要此cpp文件用到的文件,都要包含进来!这个文件的名字其实不一定要叫Circle.cpp,但非常建议cpp文件与头文件相对应。

注意到开头时有#include “Circle.h”的声明,证明我们使用到了我们刚才写的Circle类。

1.**.h叫做头文件,它是不能被编译的。“#include”叫做编译预处理指令,可以简单理解成,在1.cpp中的#include”1.h”指令把1.h中的代码在编译前添加到了1.cpp的头部。每个.cpp文件会被编译,生成一个.obj文件,然后所有的.obj文件链接起来你的可执行程序就算生成了。**

发现了没有,你要在.h文件中严格区分声明语句和定义语句。好的习惯是,头文件中应只处理常量、变量、函数以及类等等等等的声明,变量的定义和函数的实现等等等等都应该在源文件.cpp中进行。

至于.h和.cpp具有同样的主文件名的情况呢,对编译器来讲是没有什么意义的,编译器不会去匹配二者的主文件名,相反它很傻,只认#include等语句。但是这样写是一种约定俗成的编程风格,一个类的名字作为其头文件和源文件的主文件名比如Class1.h和Class1.cpp,这个类的声明在Class1.h中,实现在Class1.cpp中,我们人类看起来比较整齐,读起来方便,也很有利于模块化和源代码的重用。

为什么这个风格会约定俗成?有一句著名的话,叫“程序是为程序员写的”。

2.**h文件和cpp文件也就是说,在h文件中声明Declare,而在cpp文件中定义Define。** “声明”向计算机介绍名字,它说,“这个名字是什么意思”。而“定义”为这个名字分配存储空间。无论涉及到变量时还是函数时含义都一样。无论在哪种情况下,编译器都在“定义”处分配存储空间。对于变量,编译器确定这个变量占多少存储单元,并在内存中产生存放它们的空间。对于函数,编译器产生代码,并为之分配存储空间。函数的存储空间中有一个由使用不带参数表或带地址操作符的函数名产生的指针。定义也可以是声明。如果该编译器还没有看到过名字A,程序员定义int A,则编译器马上为这个名字分配存储地址。声明常常使用于extern关键字。如果我们只是声明变量而不是定义它,则要求使用extern。对于函数声明, extern是可选的,不带函数体的函数名连同参数表或返回值,自动地作为一个声明。

image-20201221162400018

6、内联函数

类中的内联函数应该与类的定义放在同一个头文件中(声明和定义)

7、友元

友元函数

友元函数是在类外定义的一个函数,它不是本类的成员函数,而是一个普通函数或者其他类的成员函数。

若在类中声明一个函数为友元函数,则它可以操作类中的所有数据。

一个函数可以是多个类的友元函数,只要在各个类中分别声明就行。

C++中不允许将构造函数、析构函数和虚函数声明为友元函数。

(构造函数、析构函数一定是类内的函数,而虚函数也只能是类内的函数,并且不可以是static静态成员函数)

友元类

在类A中声明类B为友元类,则类B中的所有成员函数都是类A的友元函数,都可以操作类A的所有数据。