暂定月计划:1实现DSA基础算法 2完成一个STL库 3在Linux上加强相关知识 4git巩固 5OJ增强代码实现能力
今天先快读复习一遍C++知识点,(想把peral读完==)
1 引用
- 交换两个数示例:
C语言中:
void swap(int *a, int* b)
{
int tmp;
tmp = *a; *a = *b; *b = tmp;
}
int n1, n2;
sawp(&n1, &n2);//n1,n2的值被交换
C++中用引用
void sapw(int &a, int &b)
{
int tmp;
tmp = a; a = b; b=tmp;
}
int n1, n2;
sapw(n1, n2);
- 引用作为函数的返回值
int n = 4;
int& SetValue() {return n;}
int main()
{
SetValue() = 40;
cout<<n;
return 0;
}
- 常引用
定义引用时,前面加const
关键字,即为常引用;不能通过常引用去修改其引用的内容;但并不是说它引用的内容就不能被修改,是可以通过其他方式被修改的 (跟常量指针一个道理,不能通过常量指针修改内容)
int n = 100;
const int & r = n;//常引用
r = 200;//编译错
n = 200;//没问题
-
const T &
和T&
T &
类型的引用或者T
类型的变量可以用来初始化const T &
类型的引用,反之不可以,除非用强制类型转换;同理,不能把常量指针赋值给非常量指针,而反之可以,除非强制类型转换;
常常会在函数内将函数参数定义为常量指针,就可以避免函数内部不小心改变参数指针所指地方的内容
2 动态内存分配
用new
运算符实现动态内存分配,(在C中用malloc
分配)
方法1:
P = new T;
T
是任意类型名,P
是类型为T*
的指针;动态分配出一片大小为sizeof(T)
字节的内存空间,并且将该内存空间的起始地址赋值给P
int *pn; pn = new int; *pn = 5;
方法2:
P = new T[N];
分配出N* sizeof(T)
的内存空间,并将该内存的起始地址赋值给P
用delete
运算符进行释放,必须指向new
出来的空间,注意一个空间不能被delete
多次,举例delete释放动态分配的数组:
int *p = new int[20];
p[0] = 1;
delete[] p;//删除new出来的一个数组一定要加一个[]
3 内联函数、函数重载、函数缺省参数
-
内联函数
函数调用是有时间开销的,如果函数本身只有几条语句,执行非常快,那么当这样简单的函数被反复执行很多次的时候,相比之下调用函数函数所产生的开销反而显得比较大。
(在调用一个函数时,首先要把参数、返回地址放入到栈中,当函数执行完需要从栈里取出返回地址,再跳转到返回地址去执行,这些都是额外的开销)
为了减少函数调用的开销,引入了内联函数机制。编译器处理对内联函数的调用时,是将整个函数的代码插入到调用语句处,就不会产生调用函数的语句。 在函数前加inline
关键字;
-
函数重载
一个或多个函数,名字相同,然而参数个数或者参数类型不相同,为函数重载;函数重载使得函数命名变得简单了。编译器根据调用语句中的实参的个数和类型判断应该调用哪个函数
-
函数的缺省参数
C++中,定义函数的时候可以让最右边的连续若干个参数有缺省值,那么调用函数的时候,若相应位置不写参数,参数就是缺省值
void func(int x1, int x2 = 2, int x3 = 3){}
func(10);//等效于func(10,2,3)
func(10,,8);//不行,只能最右边的连续若干个参数缺省
函数重载目的是为了提高程序的可扩充性
即如果某个写好的函数要添加新的参数,而原先那些调用函数的语句,未必需要使用新增的参数,那么为了避免对原先那些函数调用语句的修改,就可以使用缺省参数√
构造函数
成员函数的一种,名字与类名相同,可以有参数,不能有返回值(void也不行);作用是对对象进行初始化,如对成员变量赋初值;但注意对象所占用的存储空间并不是构造函数分配的,构造函数只是在对象已经占了内存空间后做一些初始化的工作
类一定有构造函数,如果定义类时没有写构造函数,则编译器生成一个默认的无参数的构造函数,不做任何的操作;一个类可以有多个构造函数(函数重载);但一旦有了自己定义的构造函数,编译器便不产生无参数的默认构造函数了,如果有需要无参的,就需要自己写了;
对象生成时构造函数会被自动调用,但注意对象一旦生成,就再也不会再在其上执行构造函数了
复制构造函数
只有一个参数,即对同类对象的引用
如:X::X(X&)
或X::X(const X&)
,二者选一,参数必须是引用不能是对象,后者能以常量对象作为参数;更多情况下会用第二种形式,因为一般情况下都不会去修改实参的值
如果没有定义复制构造函数,那么编译器会生成默认的拷贝构造函数
复制构造函数起作用的三种情况:
- 当用一个对象去吃书画同类的另一个对象时
Complex c2(c1);
Complex c2 = c1;//这是初始化语句,不是赋值语句
- 如果某函数有一个参数是类A的对象,那么该函数被调用时,类A的复制构造函数将被调用
- 如果函数的返回值是类A的对象时,则函数返回时,A的复制构造函数被调用
类型转换构造函数
1定义转换构造函数的目的是实现类型的自动转换;
2 只有一个参数,而且不是拷贝构造函数的构造函数,一般就可以看做是转换构造函数;
3当需要的时候,编译系统会自动调用转换构造函数,建立一个无名的临时对象(或临时变量)(编译器对临时对象操作)
析构函数
- 加~;没有参数和返回值,一个类最多只能有一个析构函数;
析构函数对象消亡时即自动被调用,可以定义析构函数来在对象消亡前做如释放分配空间的工作;
如果定义类时没写析构函数,则编译器生成缺省析构函数,缺省析构函数什么也不做
- 在数组中,对象数组生命期结束时,对象数组的每个元素的析构函数都会被调用
举例:
class Ctest
{
public:
~Ctest(){cout<<"析构调用"<<endl;}
};
int main()
{
Ctest array[2];
cout<<"结束mian"<<endl;
return 0;
}
/*
输出结果:
结束main
析构调用
析构调用
*/
- 关于析构函数和运算符delete
delete
运算符导致析构函数调用
Ctest *pTest;
pTest = new Ctest;//构造函数被调用
delete pTest;//析构函数被调用
//
pTest = new Ctest[3];//构造函数调用3次
delete [] pTest;//析构函数调用3次
若new一个对象数组,在delete时不用[],那么只调用一次析构函数,delete一个对象
静态成员变量
普通成员变量每个对象有各自一份,而静态成员变量一共就一份,被所有对象共享;普通成员函数必须具体作用于某个对象,而静态成员函数并不具体作用于某个对象;静态成员不需要通过对象就能访问
sizeof
运算符不会计算静态成员变量
静态成员变量本质上是全局变量,哪怕一个对象都不存在,类的静态成员变量也存在;同理,静态成员函数本质上也是全局函数;
注意:必须在定义类的文件中对静态成员变量进行一次声明or初始化,否则编译能通过,链接不能通过
在静态成员函数中,不能访问非静态成员变量,也不能调用非静态成员函数
常量成员函数
class Sample
{
public:
int value;
void GetValue() const;//常量成员函数
void func(){};
Sample()}{};
};
void Sample::GetValue() const
{
value = 0;//wrong
func();//wrong 常量成员函数上不能执行非常量成员函数
}
运算符重载
本质是函数重载;
等号运算符重载示例:
String & operator = (const String & s)
{
if(this == &s)
return *this;
delete [] str;
str = new char[strlen(s.str)+1];
strcpy(str, s.str);
return *this;
}
继承关系、复合关系
虚函数和多态
在类定义中,前面有virtual
关键字的成员函数是虚函数;注意嗷,virtual
关键字只用在类定义里的函数声明中,写函数体是不用写;构造函数和静态成员函数不能是虚函数
class base
{
virtual int get();
};
int base::get(){};
虚函数可以多态,普通函数不可以
多态的表现形式:
- 派生类的指针可以赋给基类指针
- 通过基类指针调用基类和派生类中的同名虚函数时:若该指针指向一个基类的对象,那么被调用时基类的虚函数;若该指针指向一个派生类的对象,那么被调用的是派生类的虚函数
- 多态的关键在于通过基类指针或引用调用一个虚函数时,编译时不确定到底调用的是基类的还是派生类的函数,运行时才确定,这叫动态联编
- 在派生类中和积累中虚函数同名同参数表的函数,不加virtual也自动成为虚函数
- 每一个有虚函数的类(或有虚函数的类的派生类)都有一个虚函数表,该类的任何对象中都放着虚函数表的指针。虚函数表中列出了该类的虚函数地址。每个对象会多出来的4个字节就是用来放虚函数表的地址的
- 堕胎的函数调用语句被编译成一系列根据基类指针所指向的(或基类引用所指向的)对象中存放的虚函数表的地址,在虚函数表中查找虚函数地址,并调用虚函数的指令
-
输入输出和模板
输入输出流,文件读写(略略略)
函数模板和类模板
在有多个函数和函数模板名字相同的情况下,编译器处理一条函数调用语句的顺序:
1)先找参数完全匹配的普通函数(不是由模板实例化而得的函数)
2)再找参数完全匹配的模板函数
3)再找实参经过自动类型转换后能够匹配的普通函数
4)上面的都找不到,则报错
编译器由类模板生成类的过程叫类模板的实例化,由类模板实例化得到的类叫模板类
同一个类模板的两个模板类是不兼容的
模板标准库STL
- string类
string类时模板类typedef basic_string<char> string
使用string类要包含头文件
初始化举例:
string s1("Hello");
string month = "March";
string s2(8,'x');//8个x
可以用“=”赋值,assign复制,compare比较,substr子串,sawp交换,find寻找,rfind倒找,erase删除,replace替换,insert插入,c_str转换成char*字符串
vector、deque、list
set、map、multiset、multimap
stack、queue、priority_queue
函数对象
若一个类重载了运算符“()”,则该类的对象就成为函数对象