目录
1. C++ 在delete指针后赋值为nullptr
①C++标准规定:delete空指针是合法的。
② delete是释放指针指向的内存,并不是指针本身占有的内存。所以delete后,指针还是指向那个区域,并未清零,下次使用时,会出现空间不能访问的异常,so delete后要赋值为空。
2. c++中赋值表达式返回的是左值的引用
①赋值运算符的重载:浅拷贝(单纯使两个指针指向同一个地方)与深拷贝(重新开辟一块内存,将要复制的指针指向的地方的内容复制过来)
注:减少深拷贝次数的方法:右值引用(右值:一般来说,不能取地址的表达式,就是右值, 能取地址的,就是左值)
示例:
class A { };
A & r = A(); // error , A()是无名变量,是右值
A && r = A(); //ok, r 是右值引用
移动构造函数:参数右值引用
将s.str指针赋值给str,然后令s.str指向新开辟的空间,效率高,但是改变了s.str指针指向的内容(临时对象不影响)
与深拷贝区别:删除str指向的空间,重新开辟,然后将s.str内容复制过来
}// move constructor
String(String && s):str(s.str) {
cout << "move constructor called"<<endl;
s.str = new char[1];
s.str[0] = 0;
}
调用方法:
template <class T>
void MoveSwap(T& a, T& b) {
T tmp(move(a));// std::move(a)为右值,这里会调用move onstructor
a = move(b);// move(b)为右值,因此这里会调用move assigment
b = move(tmp);// move(tmp)为右值,因此这里会调用move assigment
}
②赋值运算符“=”只能重载为成员函数
注意点:
返回值String &;
判断:if( this == & s):避免s=s的意外;
delete [] str; 删除str之前指向的内容,避免内存泄漏
str = new char[strlen(s.str)+1]; 重新开辟空间,避免指向同一内存空间
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;
}
为 String类编写复制构造函数的时候,会面临和 = 同样的问 题,用同样的方法处理。
String( String & s)
{
str = new char[strlen(s.str)+1];
strcpy(str,s.str);
}
3. 基类派生类:继承、静态函数、虚析构函数
(!!!相当相当好的一道题 – 哈哈哈)
class填空,使得输出为:
0 animals in the zoo, 0 of them are dogs, 0 of them are cats;
3 animals in the zoo, 2 of them are dogs, 1 of them are cats;
6 animals in the zoo, 3 of them are dogs, 3 of them are cats;
3 animals in the zoo, 2 of them are dogs, 1 of them are cats
#include <iostream>
using namespace std;
// 在此处补充你的代码
class Animal
{
public:
static int number;
Animal (){++number;
cout<<"animal"<<endl;};
virtual ~Animal(){--number;cout<<"animal D"<<endl;};
};
int Animal::number=0;
class Dog:public Animal
{
public:
static int number;
Dog (){++number;
cout<<"dog"<<endl;};
~Dog(){--number;cout<<"dog D"<<endl;};
};
int Dog::number=0;
class Cat:public Animal
{
public:
static int number;
Cat (){++number;
cout<<"cat"<<endl;};
~Cat(){--number;cout<<"cat D"<<endl;};
};
int Cat::number=0;
void print() {
cout << Animal::number << " animals in the zoo, " << Dog::number << " of them are dogs, " << Cat::number << " of them are cats" << endl;
}
// 在此处结束补充你的代码
int main() {
print();
Dog d1, d2;
Cat c1;
print();
Dog* d3 = new Dog();
Animal* c2 = new Cat;
Cat* c3 = new Cat;
print();
delete c3;
delete c2;
delete d3;
print();
}
涉及知识点:
- number为静态成员变量原因:Animal::number (通过类名访问)
- 派生类和基类中同名的静态变量没有任何关系,但是如果派生类继承了基类的静态成员变量,则是一份,内容和地址都相同
- 继承的原因:派生类在利用构造函数进行初始化时 ,先执行基类的构造函数;
- 基类使用虚析构函数:确保执行正确的析构函数版本(Animal* c2)如果我们要删除的是一个指向派生类对象的基类指针,则需要虚析构函数。
**ps:**把基类的析构函数声明为virtual
— >派生类的析构函数可以virtual不进行声明
—>通过基类的指针删除派生类对象时,首先调用派生类的析构函数,然后调用基类的析构函数
—>一般来说,一个类如果定义了虚函数,则应该将析构函数也定义成虚函数。或者,一个类打算作为基类使用,也应该将析构函数定义成虚函数。
!!!注意:不允许以虚函数作为构造函数
4、C++增强程序可扩充性的办法
①缺省参数的设置:C++允许最右边设置连续的默认参数;
②多态:利用虚函数实现派生类的指针/对象可以赋给基类指针/引用,通过基类指针/引用调用基类和派生类中的同名虚函数时,调用虚函数的类别取决于指向或者引用的对象。
5、多态
①用基类指针数组存放指向各种派生类对象的指 针,然后遍历该数组,就能对各个派生类对象 做各种操作,是很常用的做法
CBase * pBase[100];
②在构造函数和析构函数中调用虚函数,不是多态。编译时即可确定,调用的函数是自己的类或基类中定义的函数,不会等到运行时才决定调用自己的还是派生类的函数。
③派生类中和基类中虚函数同名同参数表的函数,不加virtual也自动成为虚函数
④多态实现原理:动态联编
“多态”的关键在于通过基类指针或引用调用 一个虚函数时,编译时不确定到底调用的是基类还是派生类的函数,运行时才确定。
多态的函数调用语句被 编译成一系列根据基类指 针所指向的(或基类引用 所引用的)对象中存放的虚函数表的地址,在虚函 数表中查找虚函数地址,并调用虚函数的指令。
6、纯虚函数与抽象类
纯虚函数是在基类中声明的虚函数,它在基类中没有定义,但要求任何派生类都要定义自己的实现方法。
在基类中实现纯虚函数的方法是在函数原型后加"=0",例如:
virtual void func() =0;
✔声明了纯虚函数的类是一个抽象类。所以,用户不能创建类的实例,只能创建它的派生类的实例。
✔在抽象类的成员函数内可以调用纯虚函数,但是在构造函数或析构函数内部不能调用纯虚函数。
✔所以类纯虚函数的声明就是在告诉子类的设计者,“你必须提供一个纯虚函数的实现,但我不知道你会怎样实现它”。
7、浮点数比较大小,不能用==
8、函数对象
是个对象,但是用起来像是函数调用,实际上也是函数调用。
class Cmy{
public:
double operator()(int a1,int a2,int a3)//重载()运算符
{
return (double)(a1+a2+a3)/3;
};
Cmy average;//函数对象
cout<<average(3,2,3);//average.operator()(3,2,3)//用起来看起来像函数调用
深刻理解:
/*
程序填空输出指定结果
输入
多组数据
每组数据两行
第一行是两个整数 m 和 n
第二行先是一个整数k ,然后后面跟着k个整数
输出
对每组数据,按原顺序输出第二行的后k个整数中,大于m且小于n的数
输出两遍
数据保证一定能找到符合要求的整数
输入样例
1 3
1 2
8
5 1 2 3 4 9
输出样例
2,
2,
3,4,
3,4,
*/
#include <iostream>
#include <vector>
using namespace std;
struct A {
int v;
A() { }
A(int n):v(n) { };
bool operator<(const A & a) const {
return v < a.v;
}
};
//your code starts here
template <class T>
class FilterClass
{
private:
T minV,maxV;
public:
FilterClass(T mi,T ma):minV(mi),maxV(ma) { };
bool operator()(const T & v) const {
return minV < v && v < maxV;
}
};
//your code ends here
template <class T>
void Print(T s,T e)
{
for(;s!=e; ++s)
cout << *s << ",";
cout << endl;
}
template <class T1, class T2,class T3>
T2 Filter( T1 s,T1 e, T2 s2, T3 op)
{
for(;s != e; ++s) {
if( op(*s)) {
* s2 = * s;
++s2;
}
}
return s2;
}
ostream & operator <<(ostream & o,A & a)
{
o << a.v;
return o;
}
vector<int> ia;
vector<A> aa;
int main()
{
int m,n;
while(cin >> m >> n) {
ia.clear();
aa.clear();
int k,tmp;
cin >> k;
for(int i = 0;i < k; ++i) {
cin >> tmp;
ia.push_back(tmp);
aa.push_back(tmp);
}
vector<int> ib(k);
vector<A> ab(k);
vector<int>::iterator p =
Filter(ia.begin(),ia.end(),ib.begin(),
FilterClass<int>(m,n));
Print(ib.begin(),p);
vector<A>::iterator pp = Filter(aa.begin(),aa.end(),ab.begin(),
FilterClass<A>(m,n));
Print(ab.begin(),pp);
}
return 0;
}
解读:
Filter是个类模板,
FilterClass是函数模板,
调用:vector<A>::iterator pp =Filter(aa.begin(),aa.end(),ab.begin(),FilterClass<A>(m,n));//实参m,n,传给构函数,用来初始化。
FilterClass(T mi,T ma):minV(mi),maxV(ma) { };
原型:T2 Filter( T1 s,T1 e, T2 s2, T3 op)
调用: if( op(*s))//op是函数指针,参数传给()重载那块
原型: bool operator()(const T & v) const
9、如何理解二级指针,怎样正确使用二级指针
就是指针的指针。
例如,你想传递指针到函数里(比如,没有头结点的链表),又想把函数中对该指针的改变传递出来(比如,该链表的第一个结点被删除,因此该指针值需要改变),这种情况除了通过函数返回变化后的指针值外,另一个方法就是传递指针的指针。