第一周 从C到C++
第一节课 函数指针
就是一个指向函数的指针,赋值后也可以直接当函数用
定义形式
返回值类型名 (*指针变量名)(参数类型1,参数类型2……)
int (*pf)(int, char)
使用方法
#include<stdio.h>
int PrintMin(int a, int b){
pass
}
int main(){
void (*pf)(int,int);//先声明一个函数指针
int x = 4,y = 5;
pf = PrintMin;//把之前建立的函数赋值给这个指针,就可以把这个指针当函数用了
pf(x,y);
return 0;
}
函数指针与qsort函数
qsort是c语言自带的一个快速排序函数,但是它的一个参数是另一个函数,即需要自己编写两个数比较的规则.
void qsort(void *base, int nelem, unsigned int width, int (*pfCompare)(const void *, const void *))
*pfCompare:调用pfCompare比较两个元素,若返回负整数,则元素1应排在前面,反之后面
*base:待排序数组的起始地址,即数组名
nelem:待排序数组的元素个数
width:待排序数组的每个元素的大小,以字节为单位
实例
调用qsort函数,将一个unsigned int 数组按照个位数从小到大排序,比如8,23,15,排序后为23,15,8
#include<stdio.h>
#include<stdlib.h>
int myCompare(const void *a, const void *b){
unsigned int *first , *second;
first = (unsigned int *)a;
second = (unsigned int *)b;//此处不能直接用*b,因为传入的a为const void *类型,见注释
return (*first%10)-(*second%10);
}
#define NUM 5
int main(){
unsigned int num[NUM] = {8,123,11,10,4};
int (*pfCompare)(const void *, const void *);
pfCompare = myCompare;
qsort(num,NUM,sizeof(unsigned int),pfCompare);//之后数组已被排序修改
for(i = 0;i<NUM;i++)
print("%d ",num[i]);//遍历打印数组
return 0;
}
- 数组初始化用{}
- 函数名字和函数指针类型是匹配的
- void *为不确定类型指针,void *赋给其他类型的变量时,需要进行强制类型转换
- 定义myCompare时参数不能为unsigned int *a,不然不能赋值给pfCompare并传给qsort
第二节课 命令行参数
使用方法
使用命令行参数时,需要在main函数加入参数,这样通过命令行执行程序时main会接收两个参数,这两个参数是os自动根据命令行处理好后交给main的。
int main(int argc, char *argv[]){
....
}
argc:用命令行执行程序时,参数的个数,包括程序文件名本身(可以用来作遍历数组时的循环边界)
argv:参数指针数组,每个元素都是char *类型,比如argv[0]就是第一个参数,即程序文件名,argv[1]就是第二个参数,随便用户传入的是什么就是什么第
第三节课 位运算
按位与 &
作用:清零某些位并保持其他位不变(清零对应0,不变对应1),或者判断某些位是0是1(和1与)
按位或 |
作用:将某些位置1其他位不变(置1和1或,不变和0或)
按位异或 ^
作用:将某些位取反,其他位不变(任何数与1异或取反,与0异或不变)
特点:
- 若a^b=c,则c^b=a,且c^a=b,穷举法可证
- 可以用异或运算交换变量值,而不用临时变量
int a = 5, b = 7;
a = a^b;
b = b^a;
a = a^b;
穷举法可证
按位非 ~
单目运算符,就是取反码。用法: ~21
左移运算符 <<
作用:a<<b,a左移b位,但并不会改变a的值,只给出结果。相当于乘法,移动时高位丢弃,低位添0
右移运算符 >>
作用:除法,除不尽往小取整。右边低位丢弃,左边高位添符号位
第四节课 引用
概念
int &a = b
a为b的引用,也就是说a与b等价,改变a的值就会改变b的值,反之同理
- a定义为b的引用之后就一直为b的引用,就算赋值a=c,也不会把a变成c的引用,而是把c的值赋给a
- a在声明时需要用变量初始化,不能用常量
用法
其实算是c++独有用法,用来代替指针,简化函数使用时的写法
void swap(int &a,int &b){
int temp;
temp=a;a=b;b=temp;
}
int n1,n2;
swap(n1,n2)
此处不用传入n1,n2的指针也能直接通过函数改变n1,n2的值,因为传进去时相当于int &a=n1;int &b=n2;
常引用
const int & a = b;
即为常引用
特点:不能通过常引用修改变量。也就是说,不能再直接改变a的值,但可以直接改变b的值
第五节课 const关键字和常量
定义常量
c++最好用const定义常量而不是用c的define,因为const是一个类型const int value = 23;
定义常量指针
const int * p = &n
类似于常引用,不可以通过常量指针修改
- 不能把常量指针(有const的)赋值给非常量指针,但反过来可以
int *x;x=p;//报错
但强制类型转换后可以x=(int *)p;
- 调用函数时,就相当于先做一个赋值操作,把传入的参数赋值给形参给函数内使用。
第六节课 动态内存分配
用法1 只分配一个变量的内存
P = new T;
P是变量指针,T是类型名,于是为P分配了sizeof(T)的内存
eg.int * p = new int; *p = 5;
用法2 分配一个数组的内存
相当于动态声明了一个数组
P=new T[N];
N可以是表达式,P为数组名,即数组起始地址,可以用P[n]获取数组元素
new运算符的返回值类型:
new T与new T[N]都返回T *,即new int 返回int *类型=>int * p = new int ;两边类型相同
释放一个内存空间
delete 指针 即可。
如int * p = new int;delete p;
释放动态分配的数组
delete []指针
如int * p = int[20];delete []p;
第七节课 内联函数与运算符重载
内联函数
用法:函数定义时前面加inline即可
inline int Max(int a,int b){
pass
}
原理:节省函数调用指令,直接把函数体与传入参数结合,处理后成为一段代码加入原代码
优点:节省函数调用指令,减少函数调用的开销,对于短函数,重复执行时效率较高
缺点:可执行程序代码变长体积变大
函数重载
概念:函数名相同,参数个数或参数类型不同。编译器根据传入参数确定使用哪个函数
第八节课 函数参数缺省
即python中的默认参数,在定义时赋值即可
第二周 类和对象初探
前两节太简单,略去
第三节课 类的使用初探
类的成员函数声明方法
普通方法:直接在类里声明同时定义
class CRectangle(){
public:
int w,h;
void Init(int w_, int h_){
w = w_;
h = h_;
}
int Area(){
return w*h;
}
};
特殊方法:在类定义里只声明函数,可以在类外定义函数,用双冒号::
class CRectangle{
public :
int w,h;
int Area();
};
int CRectangle::Area(){
return w*h;
};
调用类内成员的方法
- 对象名.成员名
- 指针->成员名
CRectangle *p1 = &r2;p1->w=5;
- 引用名.成员名:引用其实就相当于起别名,所以实际跟第一种相同
第四节课 类成员的可访问范围
private: 只能在成员函数内被访问
public: 可以在任何地方被访问
protected: 指定保护成员
缺省为私有成员
对象成员的访问权限
类的成员函数内部
- 当前对象的全部属性、函数
- 同类其他对象的全部属性、函数
类的成员函数以外的地方
- 只能够访问该类的公有成员(public)
第五节课 构造函数
作用:名字与类名相同,不能有返回值,在对象生成时自动调用,对对象初始化
构造函数与初始化函数的区别:__init__函数不是必须要有的,而且初始化函数必须自己调用才会去初始化,构造函数在对象生成的时候就自动被调用了
类的初始化
- 可以直接 类名 对象名;eg.
Complex cl;
- 也可以 类名 *对象指针 = new 类名;eg.
Complex *pc = new Complex;
- 当有自定义的构造函数时,实例化时要传入参数。eg.
Complex cl(3) or Complex *pc = new Complex(3);
- 当实例化数组类时,参数可以用{}赋值传入.eg.
CSample array[2]={4,5};
Note:只声明指针并不会初始化对象.Complex *p;
并不会调用构造函数
第三周 类和对象进阶
第一节课 复制构造函数
构造函数的一种,用于复制对象,如果不自己编写会有个默认的。
自己编写的复制构造函数可以不执行复制操作。
eg.
Complex c1;
Complex c2(c1);//调用c2的默认复制构造函数,把c1复制到c2
注意
自己编写复制构造函数时,要加&引用:
class Complex{
public:
double a,b;
Complex(Complex & c){//自己编写的复制构造函数,参数不能用Complex c
cout<<"Copy constructor called"<<endl;
}
}
应用场景
(1)用一个对象去初始化另一个同类对象
Complex c2(c1);
(2)函数的参数是类A,则在调用函数时形参A会调用自己的复制构造函数(而不会去调用原来的构造函数,即仅调用复制构造函数)
class A{
public:
A(){};
A(A &a){
cout<<"copy constructor called"<<endl;
}
}
void func(A a1){};
int main(){
A a2;
func(a2);//此时会调用a1的构造函数
}
(3)函数的返回值是A,返回时,A的复制构造函数被调用(复制构造函数调用时,即代表原本的构造函数不会被调用)
A func(){
A b(4);
return b;
}
则调用func()时,是A的复制构造函数被调用,A的复制构造函数的参数是b
第二节课 析构函数
作用
处理对象消亡时的善后工作,比如释放用户申请的内存等
(缺省的析构函数不会释放用户申请的内存)
形式
- 函数名同类名
- 在函数名前有 “~”
- 无参数和返回值
- 一个类只有一个析构函数
调用时机
- main函数结束时未消亡的对象自动调用析构函数消亡
- 使用delete时自动调用析构函数
- 对象所在作用域结束后消亡
- 全局变量、静态变量会在程序结束前消亡
第三节课 静态成员变量和函数
关键字:static
特点
- 普通的成员变量,在对象建立时也会多一份,即每个对象有一份。
- 静态成员变量,所有同class的对象共享一份
- sizeof()会忽略静态变量
- 静态成员不具体作用于哪一个对象
访问静态成员的方法
PrintTotal()是一个静态成员函数
(1)类名::成员名
CRectangle::PrintTotal();
(2)对象名.成员名
CRectangle c;
c.PrintTotal();
(3)指针->成员名
CRectangle * r = &c;
r->PrintTotal();
(4)引用.成员名
CRectangle & ref = c;
ref.PrintTotal();
设置静态成员的目的
把和某些类紧密相关的全局变量和该类联系在一起,易于维护和理解
注意
(1)静态成员变量需要进行全局声明,即在main之前声明。
eg.
int CRectangle::nTotalNumber = 0;
int CRectangle::nTotalArea = 0;
int main(){
…………
}
(2)静态成员变量如果没有public关键字默认为私有变量,不能在外面通过类名::成员直接访问
(3)在静态成员函数中,不能访问非静态成员变量,也不能调用非静态成员函数,否则说不清楚是访问的哪一个对象的。
第四节课 封闭类
什么是封闭类?
包含成员对象的类
也就是说,类在定义时,成员变量有其他类的对象,这样的类叫做封闭类
eg.
class CTyre{ }//轮胎类
class CEngine{ }//引擎类
class CCar{
private:
int price;
CTyre tyre;
CEngine engine;
}//CCar类的成员变量里有轮胎类和引擎类,则CCar为封闭类
新的成员变量初始化方法
初始化列表
class CTyre{
int width;
public:
CTyre(int w):width(w){};//即在写构造函数时,用冒号+成员变量(传入参数){}的方式定义构造函数
}
//当然构造函数也可以在类的外边定义,用双冒号::即可,这样就是下面的形式
CTyre::CTyre(int w):width(w){};
//这样实例化对象时直接传入参数即可
CTyre tyre(3);
Note
在定义封闭类时,构造函数要明确成员对象(即成员变量中的其他类)是如何初始化的,即构造函数接收相应参数再传给他们的构造函数,否则编译会报错
第五节课 this指针
相当于python中的self,是非静态成员函数隐藏的一个参数,指向对象本身
note:
为什么是非静态?
因为静态成员函数是所有这个类的对象共有的,所以用this不知道指向的是哪个对象
this 指针有什么用?
- 可以return *this,返回对象本身
- c++编译时可以看做先翻译成c再编译,成员函数翻译成c时,变成:成员函数(对象类型 *this, 其他参数)
第六节课 常量对象和成员函数
常量对象
在实例化时,在定义对象前加const关键字,则实例化后的对象为常量对象,对象的值一经初始化不可修改
常量成员函数
在成员函数定义时,在函数说明后面加const关键字
eg.void Sample::GetValue() const {};
特点
- 常量成员函数执行期间,不能修改成员变量的值,不能调用其他非常量成员函数(否则其他成员函数可能修改成员变量的值),静态成员变量/函数除外(因为静态成员变量/函数本质上说不属于这个对象)
- 常量对象不能执行非常量成员函数
- 同名成员函数加const算重载
常引用
(1)为什么要用对象的引用?
因为如果直接用对象的话,会运行对象的复制构造函数,增加程序开销
eg.
class Smaple{};
void PrintfObj(Sample o){};//直接用对象做形参
void PrintfObj(Sample & o){};//用对象的引用做形参
(2)为什么要用常引用?
当我们不希望传入的对象被修改时,则用常引用,即加const关键字
vodi PrintfObj(const Sample & o){};
第四周 运算符重载
第一节课 运算符重载的基本概念
什么是运算符重载?
就是使普通的运算符能有函数的作用,本质上就是函数重载
比如可以让加号“+”能直接使两个类的对象相加
程序编译时,遇到有运算符的操作其实本质上是对运算符函数的调用,操作数就是函数的参数
如何定义
返回值类型 operator 运算符(形参表){
……
}
eg.
//有一个复数类Complex
//现在把+号重载,使它能让两个Complex相加,返回相加后的Complex
Complex operator +(const Complex & a,const Complex & b){
return Complex(a.real+b.real, a.imaginary+b.imaginary)
}
Complex a(1,2), b(2,3), c;
c=a+b;
Note:
当运算符重载为成员函数时,只需要有后面那个参数,因为第一个参数默认为对象本身,函数定义时直接把成员变量拿来用就可以了,即a.real变为real即可
第二节课 赋值运算符“=”的重载
Note:赋值运算符只能重载为成员函数
这一节最好自己把代码打一遍,好好理解一下
赋值与初始化
注意,假设已定义了String类,同时重载了赋值运算符“=”
此时,可以使用
String s;
s = "Hello";
但是,不能使用String s2="Hello";
因为这条语句中的“=”是算作初始化而不是赋值,初始化时调用的是构造函数,而不会去考虑其运算符重载成员函数。
第八周 STL-1
第一节课 STL概述
STL全称 Standard template library,标准模板库
容器: 可容纳各种数据类型的通用数据结构,就是类模板
迭代器: 可用于依次存取容器中的元素,类似于指针
算法: 用来操作容器中元素的函数模板
vector 头文件
一个动态数组,一般会分配比需要更多的空间
不超过已分配的空间大小时,在末尾插入或删除,都是O(1)
若超过大小,则要重新分配空间,并把之前的数据复制过来,变成O(n)
在前面插入或删除为O(n)
deque 头文件
双向队列,相比于vector,在队列两端增删元素都是O(1)
list 头文件
双向链表
在已找到元素位置时,增删为O(1)