C++01

1.三大特性

封装,继承和多态

2.引用

1.引用的本质是指针。
2.引用必须初始化。
3.没有二级引用,但有二级指针。
4.不能使用常量初始化引用。
5.如果局部变量作为引用返回,返回的时候,内部触发一个memcpy(防止局部变量因为函数结束而被释放)

3.默认参数

1.如果函数的声明和实现分开,那么默认参数要写在声明里面。
2.一个函数可以有多个默认参数,而且从右往左依次实现,不能间隔。

4.函数重载,重写与覆盖

函数重载:
1.在相同作用域中
2.函数名相同
3.函数参数个数和类型不同
4.返回值类型不能作为函数重载的依据
函数重载的原理是名称粉碎,编译器在生成obj函数导出符号时,会考虑函数名字,参数类型,参数个数,并把这些信息放入粉碎后的名称中。

函数覆盖:
派生类函数覆盖基类函数,有以下关键点:

1.分别位于基类和派生类中
2.函数名字相同
3.参数类型列表以及顺序、以及返回值类型相同
4.基类函数必须有virtual 关键字。

Override是在在不同作用域中,多个函数原型完全一样,而实现却不同的函数,是C++实现多态的基础。

函数重写:
重写是对基类和子类中,同名函数使用规则的说明。

1.函数名相同,参数类型列表不同,此时,不论基类中定义有无virtual关键字,基类的函数将被隐藏;

2.函数名相同,参数列表相同,返回值相同,基类定义没有virtual关键字,此时,调用基类还是子类的方法,将由指针类型决定。如果对象指针为基类的类型,则调用基类的方法;如果为子类的类型,则调用子类的方法。注意: 此时与指针指向的对象的类型无关,只与指针本身的定义有关系。

5.C++中使用C编写的函数

函数前加上extern “c”,如extern “c” int MyFun(int x)
因为C++中有名称粉碎,而C中没有,所以由C生成的OBJ,与C++生成的OBJ,不做任何说明直接链接,会出现链接错误,加上extern “c”告诉编译器,这个函数,生成OBJ时,不需要名称粉碎。

6.构造函数与析构函数

1.构造函数可以重载,而析构函数不能被重载(因为构造函数可以带参数,但是析构函数不能带参数)
2.构造函数可以被显式调用也可以被隐式调用,加上explicit说明的构造函数只能被显式调用。
3.基类的析构函数最好定义成虚函数,因为当用基类的指针指向派生类的对象时,发生的是动态绑定,如果基类的析构函数不为virtual,则不会调用派生类的析构函数,容易发生内存泄露。

7.拷贝构造,深拷贝和浅拷贝

拷贝构造:
1.如果自己不定义拷贝构造函数,编译器则会帮我们生成一个默认拷贝构造。默认拷贝构造的本质是:
memcpy(&obj2,&obj1,sizeof(obj1));

2.CMyTest(const CMyTest & otherobj)

使用时:
CMyTest obj1;
CMyTest obj2(obj1);
CMyTest obj3 = obj1;

3.拷贝构造使用情况
(1)对象需要通过另一个对象进行初始化。
(2)函数的参数为类的对象时,

		void g_fun(CExample c);//函数的声明,实现这里省略不写,反正只是举个例子
		g_fun(A);//在主函数里面调用这个函数。

调用g_fun()时,会产生以下几个重要步骤:
(1).A对象传入形参时,会先会产生一个临时变量,例如C
(2).然后调用拷贝构造函数把A的值给C。 整个这两个步骤有点像:CExample C(A);
(3).等g_fun()执行完后, 析构掉 C 对象。

(3)函数的返回值是类的对象。

CExample g_fun()
{
    CExample temp(0);
    return temp;
}
int main()
{
    
    g_fun();
    return 0;
}

当g_Fun()函数执行到return时,会产生以下几个重要步骤:
(1). 先会产生一个临时变量,例如C。
(2). 然后调用拷贝构造函数把temp的值给C。整个这两个步骤有点像:CExample C(temp);
(3). 在函数执行到最后先析构temp局部变量。
(4). 等g_fun()执行完后再析构掉C对象。

4.深拷贝和浅拷贝:
默认拷贝构造也是浅拷贝,浅拷贝是指在对象复制时,只对对象进行简单的赋值,一旦对象里面存在动态成员,浅拷贝就会出问题。

#include<iostream>
#include<assert.h>
using namespace std;
class Rect
{
public:
    Rect()
    {
     p=new int(100);
    }
   
    ~Rect()
    {
     assert(p!=NULL);
        delete p;
    }
private:
    int width;
    int height;
    int *p;
};
int main()
{
    Rect rect1;
    Rect rect2(rect1);
    return 0;
}

上面的浅拷贝,rect2的p和rect1的p会指向同一个地址位置,因为浅拷贝不会为其另外申请新的内存空间。
因为调用了两次析构函数,等于同时释放了两次p指向的地址空间,出现错误。

深拷贝则重新分配动态分配空间

#include<iostream>
#include<assert.h>
using namespace std;
class Rect
{
public:
    Rect()
    {
     p=new int(100);
    }
    
    Rect(const Rect& r)
    {
     width=r.width;
        height=r.height;
     p=new int(100);
        *p=*(r.p);
    }
     
    ~Rect()
    {
     assert(p!=NULL);
        delete p;
    }
private:
    int width;
    int height;
    int *p;
};
int main()
{
    Rect rect1;
    Rect rect2(rect1);
    return 0;
}

两个不同的动态空间存放一样的内容,在析构时,分别释放掉这两个内存空间,就不会发生重复释放的错误。

8.静态成员方法和静态数据成员

1.静态成员方法可以实例化对象后调用,也可以直接调用。
2.静态成员方法本质是加了命名空间的全局函数,静态成员方法可以脱离实例化对象直接调用。
3.静态方法中,不可以使用类的普通数据成员。

4静态数据成员被所有的实例化对象共享,在类中声明,在类外初始化赋值。如果在类外定义来赋初值,则将自动初始化为0。

9.类的初始化列表

语法:在构造函数后加冒号,冒号后跟初始化列表。(其实说在构造函数后,这个描述不准确,但是不知道怎么描述,反正大家都知道是那个位置就行了,别计较哈哈)
几种必须要用初始化列表初始化的情况:
1.类成员是const型。
2.类成员是引用型。
3.类成员为没有默认构造函数的类类型。
4.如果存在继承关系,派生类必须在其初始化列表中调用基类的构造函数。
5.子类初始化父类的私有成员。

const或引用只能初始化但是不能赋值,构造函数的函数体内只能赋值而不是初始化,因此初始化const对象或引用的唯一机会是初始化列表。
从无到有为初始化,赋值是对已有的对象赋值。

10.单例模式

单例模式就是将构造函数私有化,然后通过公开的接口,获取单例。

class CSingLeton
{
	private:
		CSingLeton()
		{
		
		}
	public:
		static CSingLeton * GetInstance()
		{
			static CSingLeton * m_pInstance;
			if(m_pInstance == NULL)
				m_pInstance = new CSingLeton();
			return m_pInstance;
		}
	};

另外,在实际设计模式中,单例模式分为懒汉模式,饿汉模式以及其他模式,具体再去做了解以后再做补充。

11.基类和派生类的指针转换问题

1.派生类的指针可以在没有警告和强转的情况下自动转换为基类指针。
2.基类指针转换为派生类指针,哈,很容易出错,最好不要强转。
因为继承时的内存布局:
基类的数据放在内存的前半部分
派生类的数据放在基类的数据之后
因此派生类的内存布局的前半部分与基类一致。

12.继承中的构造和析构问题

当派生类中没有其他类的对象时,先构造基类,再构造派生类。先析构派生类,再析构基类。
当派生类有其他类的对象时,先构造基类,再构造其他类,再构造派生类。析构顺序仍然相反。

13.运算符重载

除了少数的,如:“::”,“.”,“?:”等。在C++中几乎所有的运算符都可以重载。包括[],new,delete都可以重载。

重载原则:
1.不能无中生有(不能定义新的运算符),也不要改变运算符的原有意义。
2.重载不能改变运算符的运算对象(即操作数)的个数。
3.不能改变运算符的优先级和结核性。
4.重载运算符的函数不能指定默认的参数值。
5.重载运算符函数的参数应该至少有一个是类对象(或类对象的引用),不能都是基本类型。

1.成员函数运算符

运算符重载为类的成员函数的一般格式为:

<函数类型> operator <运算符>(<参数表>)

{

	 <函数体>

}

当运算符重载为类的成员函数时,函数的参数个数比原来的操作数要少一个(后置单目运算符除外),因为成员函数用this指针隐式地访问了类的一个对象,它充当了运算符函数最左边的操作数。因此:

(1) 双目运算符重载为类的成员函数时,函数只显式说明一个参数,该形参是运算符的右操作数。

(2) 前置单目运算符重载为类的成员函数时,不需要显式说明参数,即函数没有形参。

(3) 后置单目运算符重载为类的成员函数时,函数要带有一个整型形参。

调用成员函数运算符的格式如下:

  <对象名>.operator <运算符>(<参数>)

  它等价于

<对象名><运算符><参数>

例如:a+b等价于a.operator +(b)。一般情况下,我们采用运算符的习惯表达方式。

2.友元函数运算符

运算符重载为类的友元函数的一般格式为:

friend <函数类型> operator <运算符>(<参数表>)

{

	 <函数体>

}

当运算符重载为类的友元函数时,由于没有隐含的this指针,因此操作数的个数没有变化,所有的操作数都必须通过函数的形参进行传递,函数的参数与操作数自左至右一一对应。

调用友元函数运算符的格式如下:

operator <运算符>(<参数1>,<参数2>)

它等价于

<参数1><运算符><参数2>

例如:a+b等价于operator +(a,b)。

(1) 一般情况下,单目运算符最好重载为类的成员函数;双目运算符则最好重载为类的友元函数。

(2) 以下一些双目运算符不能重载为类的友元函数:=、()、[]、->。

(3) 类型转换函数只能定义为一个类的成员函数而不能定义为类的友元函数。

(4) 若一个运算符的操作需要修改对象的状态,选择重载为成员函数较好。

(5) 若运算符所需的操作数(尤其是第一个操作数)希望有隐式类型转换,则只能选用友元函数。

(6) 当运算符函数是一个成员函数时,最左边的操作数(或者只有最左边的操作数)必须是运算符类的一个类对象(或者是对该类对象的引用)。如果左边的操作数必须是一个不同类的对象,或者是一个内部类型的对象,该运算符函数必须作为一个友元函数来实现。

(7) 当需要重载运算符具有可交换性时,选择重载为友元函数。

14.STL中的vector使用

1.vector是一种顺序容器,相当于可以动态扩展的数组,不会造成浪费内存,也不会造成越界。
2.vector在中间插入和删除慢,但在末端插入和删除快。

3.vector常用操作:
vectorV1 //定义元素为int型的vector
vectorV2 //定义元素为string型的vector
vector::iterator it; //定义一个迭代器。
V1.push_back() //在数组的最后添加一个数据。
V1.pop_back() //删除数组的最后一个数据。
V1.front() //返回上一个元素。
V1.begin() //数组头的指针,用迭代器接受。
V1.end() //数组最后一个单元+1的指针,用迭代器接受。
V1.clear() //移除掉所有元素。
V1.empty() //判断容器是否为空。
V1.erase(pos) //删除pos位置的数据。
V1.erase(beg,end) //删除[beg,end]间的数据。
V1.size()
V1.insert(pos,data) //在pos处插入data数据。
sort(V1.begin(),V1.end()) //排序。
sort(V1.begin(),V1.end(),greater()) //降序排序。

4.当vector的size == capacity()时,会自动扩容,VS下以1.5倍的方式扩容,gcc下以2倍的方式扩容。但是也不是一直无限扩容,有一个最大容量。扩容的原理其实就是拷贝数据到新的内存,然后释放掉原来的内存。

15.友元函数与友元类

友元函数
1.友元是一种说明在类外的非成员函数,友元不是成员函数,但它可以访问类中的私有成员。
2.友元提高了程序的运行效率,但它破坏了类的封装性和隐藏性,使非成员函数可以访问类中的私有成员。
3.友元函数不用const修饰。
4.友元函数可以在类的任何地方声明,不受类访问限定符限制,但是不能放在成员函数的实现里面。
5.一个函数可以是多个类的友元函数,调用与普通函数相同。

友元类
友元类是一个类可以作为另一个类的友元,意味着这个类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类的非公有成员。

注:
1.友元关系不能继承。
2.友元关系不能传递。
3.友元关系是单向的,不具有交换性。

16.引用计数

1.当创建类的新对象时,初始化指针,并将引用计数设置为1
2.当对象作为另一个对象的副本时,复制构造函数复制副本指针,并增加与指针相应的引用计数(加1)
3.使用赋值操作符对一个对象进行赋值时,处理复杂一点:先使左操作数的指针的引用计数减1(为何减1:因为指针已经指向别的地方),如果减1后引用计数为0,则释放指针所指对象内存。然后增加右操作数所指对象的引用计数(为何增加:因为此时做操作数指向对象即右操作数指向对象)。
4.析构函数:调用析构函数时,析构函数先使引用计数减1,如果减至0则delete对象。

实际用例:云盘秒传,qq快传,快捷方式。在网络资源中搜索此资源,并不是真正上传,而是在引用计数上加一。

17.纯虚函数

1.含有纯虚函数的类,无法实例化。
2.含有纯虚函数的类的派生类,如果不实现对应的纯虚函数,也无法实例化。
3.纯虚析构必须要有实现,因为virtual析构函数需要由下往上层层调用。

18.智能指针

1.引用计数类,用于管理引用计数及动态资源。
2.智能指针,用于告之引用计数对象,调用addref(),release()。
3.智能指针需要实现的方法:拷贝构造,operator = ,operator *,operator->。

常见的智能指针:unique_ptr,shared_ptr,weak_ptr,auto_ptr.(memory头文件)

unique_ptr没有拷贝构造和operator = 重载,适用于单例模式资源。

shared_ptr支持拷贝构造,operator = 重载。(但是会出现循环引用问题)。

#include<iostream>
#include"boost\shared_ptr.hpp"
#include"boost\weak_ptr.hpp"
#include<stdlib.h>
using namespace std;
struct Node
{
    boost::shared_ptr<Node> pNext;
    boost::shared_ptr<Node> pPre;
    int value;
};
int main()
{
    boost::shared_ptr<Node> p1(new Node);
    boost::shared_ptr<Node> p2(new Node);
    p1->pNext = p2;
    p2->pPre = p1;
    system("pause");
    return 0;
}

此时p1和p2的usecount都是2,当函数结束时,两个的usecount都减1,都变成1,而p1要释放,需要p2先释放,p2要释放,需要p1先释放,结果就是两个usecount都不能变为0,都不能释放,这就是循环引用的问题。

而weak_ptr生来就是为了配合shared_ptr的循环引用问题的

struct Node  
{  
    Node(int va)  
        :value(va)  
          
    {  
        cout<<"Node()"<<endl;  
    }  
  
  
    ~Node()  
    {  
        cout<<"~Node()"<<endl;  
    }  
    weak_ptr<Node> _pre;  
    weak_ptr<Node> _next;  
    int value;  
};  
  
void funtest()  
{  
    shared_ptr<Node> sp1(new Node(1));  
    shared_ptr<Node> sp2(new Node(2));  
  
    sp1->_next=sp2;  
    sp2->_pre=sp1;  
}  
int main()  
{  
    funtest();  
    return 0;  
}  

weak值为2,但是use值最后均为1,函数结束后都减为0,都被释放。
weak ptr不能用来定义一个智能指针的对象,只能将shared ptr的对象赋值给weak ptr,并且这样不会增加和改变引用计数的值

shred_ptr的线程安全问题:
shared_ptr的本身不是百分百线程安全的,它的引用计数本身安全且无锁,但对象的读写不是,因为shared_ptr有两个数据成员,读写操作不能原子化。shared_ptr的线程安全级别和内建类型,标准容器库,string一样,即:
1.一个shared_ptr实体可以被多个线程同时读取。
2.两个shared_ptr实体可以同时被两个线程写入,析构算写操作。
3.如果需要从多个线程读写同一个shared_ptr对象,需要加锁。

19.多重继承的菱形继承问题和虚继承,虚基类

菱形继承的问题:
会导致在孙子代的实例化对象中,保存多份爷爷代的类数据成员,形成冗余。而且在孙子代的实例化对象中,调用爷爷类的成员,还会产生二义性。

解决方法就是虚继承:
在父亲类这一块用virtual来修饰继承 virtual public 爷爷类名

#include <iostream>
using namespace std;
class B0// 声明为基类B0
{
    int nv;//默认为私有成员
public://外部接口
    B0(int n){ nv = n; cout << "Member of B0" << endl; }//B0类的构造函数
    void fun(){ cout << "fun of B0" << endl; }
};
class B1 :virtual public B0
{
    int nv1;
public:
    B1(int a) :B0(a){ cout << "Member of B1" << endl; }
};
class B2 :virtual public B0
{
    int nv2;
public:
    B2(int a) :B0(a){ cout << "Member of B2" << endl; }
};
class D1 :public B1, public B2
{
    int nvd;
public:
    D1(int a) :B0(a), B1(a), B2(a){ cout << "Member of D1" << endl; }// 此行的含义,参考下边的 “使用注意5”
    void fund(){ cout << "fun of D1" << endl; }
};
int main(void)
{
    D1 d1(1);
    d1.fund();
    d1.fun();
    return 0;
}

20.STL文件操作

ofstream,头文件同名,向文件中写入数据。

ofstream output;
output.open("score.txt",ios::out);
output << "daidai" << "   " << 90 << endl;
output.close();

ifstream 读取文件数据

ifstream input;
input.open("score.txt",ios::in);
char name[80];
int score;
input >> name >> score;
input.close();

ios_base::binary 以二进制形式打开文件
ios_base::trunc 打开文件先将之前的内容删除。
ios_base::app 打开文件,写入时始终从尾部添加
ios_base::ate 打开文件,将文件指针移到文件末尾
ios_base::out 打开文件,用于写入
ios_base::in 打开文件,用于读取

21.C与C++中的异常处理

C:
因为goto不能跨函数跳转,所以C库函数,提供了setjmp,longjmp函数,进行跨函数跳转。

#include<stdio.h>
#include<setjmp.h>
 
jmp_buf mark;
 
int Div(int a,int b){
    if(b==0){
        longjmp(mark,1);  //会使state = 1
    }
    return a/b;
}
int main(){
    int State = setjmp(mark);   //保存寄存器相关信息,初始值为0
    if(State==0){
        Div(4,0);
    }else{
        switch(State){
            case 1:
                printf("除0异常!\n");
        }
    }
    return 0;
}

C++:
C++中通过throw和try…catch语句实现对异常的处理。

#include <iostream>
using namespace std;
int main()
{
    double m ,n;
    cin >> m >> n;
    try {
        cout << "before dividing." << endl;
        if( n == 0)
            throw -1; //抛出int类型异常
        else
            cout << m / n << endl;
        cout << "after dividing." << endl;
    }
    catch(double d) {
        cout << "catch(double) " << d <<  endl;
    }
    catch(int e) {
        cout << "catch(int) " << e << endl;
    }
    cout << "finished" << endl;
    return 0;
}

22.C11新特性

1.auto自动类型推导
2.for…range循环
3.lambda函数
4.仿函数与bind
5.右值引用等等。
待补充和了解。

有错误欢迎向博主指正,博主会在以后对具体的知识做具体的代码和知识点的补充。

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值