C++高级编程

C++高级编程

基本类的构成

  1. 头文件使用include包含到程序中

    • #include 表示对于系统标准库的引用
    • #inuclde "selfWrite.h"表示对于自己写的库的引用
    • 拓展名可能不是.h甚至不使用拓展名
  2. 任何一个头文件都要加上的防卫式声明:避免不同头文件重复定义的声明冲突,通常使用头文件名大写并前后加下划线

    #ifndef _FILENAME_H
    #define _FILENAME_H
    
    ···
        
    #endif
    
  3. C++模板类的作用:避免类型不同而函数相同的重复类的定义

    template
    class complex{
    public:
        complex (T r = 0, T i = 0)// 正规的构造函数初始化
            :re(r), im(i)// 构造函数的构造初始化,不写到下面,下面是进行赋值用的
            {}
        complex& operator += (const complex&);// 只是一个声明,可以在body外定义
        T real() const { return re;}
        T imag() const { return im;}
    private:
        T re, im;
        friend complex& _doapl(complex*, const complex&);
    };
    //模板类的使用
    int main()
    {
    	complex c1(2.5, 1.5); // 将complex模板类中的T全部替换为double
        complex c1(2.5, 1.5);
    }
    
  4. inline函数以代码膨胀为代价,消除了调用时候的堆栈开销,从而提高了函数的执行效率。通常简单的函数使用inline,复杂的函数使用inline可能编译器不会认可

  5. 构造函数

    • 创建对象时,会被自动调用
    • 构造函数名与类名相同
    • 参数可以有默认参数,如果创建对象时未指定则使用该参数
    • 可以使用构造初始化
    • 不带指针的函数通常不用写析构函数
    • 构造函数可以进行重载,即不同的初值形式
    • 空构造函数与含默认参数的构造函数不能进行重载,因为难以同时调用
  6. 构造函数放在private中无法通过通常形式创建对象,因为外界无法调用构造函数,但是可以通过单例模式进行使用

  7. 正规:类内不改变对象值的函数加const,目的是将类内函数变成只读函数
    eg:T fun() const {return value}

  8. 声明对象时,在对象类型前加const,表示对象内的值只读,但是调用对象内的取值函数如果未加const也会报错,因为对象声明只读但对象内的函数却有写的权力

  9. 参数传递

    • 传值:传递参数的整体,开销大
    • 引用:所要传递参数的首地址(相当于c的指针,但最好都传引用,除了小于指针的参数,eg:char类型)
    • 常量引用:传的引用内容不希望被修改,则函数定义形参时加const
  10. C++是面向对象语言中效率最好的,所以要将每个影响习惯的小细节内化为习惯,才能编写高效高质量的C++程序

  11. 友元函数:可以直接使用类内封装好的数据,而不需要通过取值函数

  12. 友元函数可以直接使用类内封装的数据,而其他函数必须经过类内的取值函数调用,效率低但是不会破坏封装性

  13. 相同类的各个对象互为友元,不破坏封装性

    class complex{
        public:
        	comoplex(double r = 0, double i = 0)
                :re(r), im(i)
            { }
        // 同类对象的直接调用
        int func(const complex& param){
            return param.re + param.im;
        }
        
        private:
        	double re, im;
    };
    
    int main()
    {
        complex c1(2,1);
        complex c2;
        c2.func(c1);// 不破坏封装性
    }
    
  14. 函数运算结果的存放

    • 必须在函数内创建存储变量进行存储,但是函数调用完成,内部的引用内存释放会导致返回的结果指向为空
    • 使用传递的非常量引用的实参进行存储
  15. 任何函数都有一个隐含的this指针参数,指向调用该函数的对象

  16. 使用引用的好处:传递者无需知道接收者的形式,即return返回对象可以和函数类型不同

  17. 通常不要把函数设置成void类型,要设计成目的操作数类型,避免出错

  18. 需要使用返回值的函数,不能返回引用,因为函数在调用结束后对释放内部的引用内存区域

  19. 类型名称( 操作 )创建一个临时对象用来存放该操作的结果,不需要声明名称,在下一行就释放内存,速度快。常用于函数值的返回

  20. 函数返回值需要在函数中进行创建,则以值的形式返回,避免函数堆栈释放引起的无效引用
    函数返回值可以使用参数,则可以使用参数的引用进行返回,速度快

带指针的类

  1. 接受带指针参数的类需要三个基本函数:

    • 拷贝构造:动态创建足够的空间,将指针指向的内容进行复制过来
    • 拷贝赋值:将原来的内容销毁创建新的空间,将新的内容复制过来
    • 析构函数
    // 正规:类内只写函数的声明和参数类型,函数的body在类外实现并加inline
    class String
    {
        public:
        	String(const String& str);// 拷贝构造函数
        	String& operator=(const String &str);// 拷贝赋值函数
        	~String();// 析构函数
        private:
        	char * m_data;
    };
    inline String::String (kconst char* cstr = 0){
        if(cstr){// 非空字符串
            m_data = new char[strlen(cstr) + 1];
            strcpy(m_data, cstr);
        }else{	// 空字符串
            m_data = new char[1];
            *m_data = '\0';
        }
    }
    inline String& String::operator=(const String& str){
        // 如果没有自我赋值检测,在销毁自我后会导致复制内容也被销毁而出错
        if(this ==  &str) // 检测是否为自我赋值,是则直接返回自己
            return *this; 
        //销毁,创建,复制
        delete [] m_data;
        m_data = new char[strlen(str.m_data) + 1];
        strcpy(m_data, str.m_data);
        return *this;
    } 
    inline String::~String(){
    	delete[] m_data;    
    }
    
  2. 动态分配的内存空间在使用完成后要及时释放,如果没有释放则为内存泄漏,即new和delete必须搭配使用

  3. 类内指向的动态分配的内容不属于类,进行对象copy时,不会复制一份,而是与原对象指向相同

  4. String s1(s2) 等同于 String s2 = s1

  5. 别名是一件危险的事情,尽量每个对象都有自己的完整的copy

  6. 当调用函数时,函数本身会形成一个stack用来放置它所接收的参数和返回地址

  7. heap是由操作系统提供的一块全局的内存空间,程序可动态分配从中获得一块内存区域

  8. static在作用域结束后仍然存在,直到整个程序结束

  9. new一个对象的过程

  • 分配内存:分配一片对象大小的内存空间,并使用void*指向
  • 转型:将void*转化成对象类型指针
  • 构造函数:调用对应的构造函数(谁调用,谁负责)
  1. delete一个对象的过程

    • 调用析构函数释放动态分配的内存空间
    • 释放指针自身所占的内存空间
  2. 在VC编译器下,malloc()函数会申请16的倍数的大小的内存空间由三部分组成

    • 创建对象所需内存大小
    • 将内存空间变成最近的16的倍数的pad
    • 首尾分别有一个4Byte的cookie进行表示内存空间的大小和占用情况
  3. new和delete在array下要注意

    m_data = new char[strlen(cstr) + 1];
    delete[] m_data; // 注意[ ]的使用
    
  4. 对象的创建和销毁

```c++
class Complex{···}
···
{
    Complex* p = new Complex;
    ···
    delete p;
}
```
  1. 对象的地址实际相当于调用该类对象的this指针,即对象调用类内函数是将对象名地址作为this指针进行传递调用的

    complex c1;
    cout << c1.real(); // 实际相当于complex::real(&c1);
    
  2. 类内的数据和函数都可以使用static进行修饰

    • 静态数据:所有该类的对象共用一份
    • 静态函数:没有this指针指向,只能处理静态数据
  3. 静态函数的调用

    • 通过对象进行调用
    • 通过类名进行调用
  4. 使用静态函数实现Singleton单例模式

    // 将静态变量函数封装到外面,因为静态变量只有当其函数被调用时才会被初始化
    class A{
    	public:
        	static A& getInstance();
        	setup(){···}
        private:
        	A();
        	A(const A& rhs);
        	···
    };
    
    A& A:: getInstance(){
        static A a;
        return a;
    }
    
  5. class template 类模板

    template
    class complex{
    	public:
        	complex(T r=0, T i=0)
                :re(r), im(i)
            {}
        	complex& operator += (const complex&);
        	T real() const{return re;}
        	T imag() const{return im;}
        private:
        	T re,im;   	
    };
    // 使用类模板
    {
        complex c1(2.5,1.5);// 将模板类内的T全部替换成double
        complex c1(2,5);
        
    }
    
  6. 标准库内的函数调用

    • 直接使用:using namespace std;
    • 声明使用:using std::cout
    • 单一使用:使用时,必须std::
  7. 面向对象的关系

    • 复合(Compositioin)
      • A拥有B(可以用于改造原有功能类,即Adapter)
      • 构造由内而外,外部类首先调用内部类的默认构造函数后再执行自己
      • 析构由外而内,外部类的析构函数首先调用执行析构自己,再析构内部类
    • 委托(Delegation)
      • 类A内有指向类B的指针
      • 编译防火墙,指针指向并不用真正实例化
      • 不可更改的共享,更改需要单独给一个副本
    • 继承(Inheritance)
      • 子类继承了父类函数的调用权
      • 构造由父及子,子类实例化先调用父类构造函数后调用子类构造函数
      • 析构由子及父,析构子类对象先析构子类后析构父类
  8. 虚函数的类别

    • non-virtual函数:不希望子类重写,int objectA() const
    • virtual函数:已有默认定义,希望子类重写,virtual void error(int a);
    • pure virtual函数:没有默认定义一定要重写,virtual void draw() const=0;
  9. 纯虚函数的子类一定要重写,空函数的不一定

  10. 继承的本质

    • 统一性:子类的对象可以调用父类的函数
    • 多样性:父类的虚函数可以进行重写改造,调用中优先执行子类的重写虚函数
  11. 谁调用,谁负责,谁就是类的this指针

  12. 委托(Delegation)+继承(Inheritance):可以实现一份数据多种方式显示

  13. 类内的static,要通过类名在类外进行初始化

程序设计与对象模型

  1. 勿在浮沙筑高台

  2. 标准库时使用模板编程的思维做出来的、

  3. 尽量使用const,因为它可以接受const和非const参数

  4. 转换函数

    class Fraction	// 转换成分数转化为小数的类
    {
    public: // 首先定义public函数并定格写
        // 先写构造函数,注意初始化的赋值格式
        Fraction(int num, int den=1) 
    		: m_numerator(num), m_denominator(den){}
        // 该对象发生double强制类型转换时候会自动调用
        operator double() const{ 
            return (double)(m_numerator / m_denominator);
         // 运算符的重载
         Fraction operator + (const Fraction& f){
             return Fraction(···);
         }
        }    
    private: // 后写private
        int m_numerator; // 分子
        int m_denominator; // 分母
    };
    int main(){
        Fraction f(3, 5);
        double d = 4 + f; // 自动调用f的double函数
        Fraction d2 = f + 4; // error,方法调用二义性,可在构造函数前加explict
    }
    
  5. 无二义性:当编译器发现同时的多条符合的方法调用时,会报错

  6. explict关键字通常用于构造函数前面, 是防止类构造函数的隐式自动转换

  7. 智能指针类:将类设计的像指针一样但是比指针更聪明

    template
    class shared_ptr
    {
    public:
        //c++满足指针行为的两个函数
        T& operator*()const{
            return *px;
        }
        
        T* operator->()const{
            return px;
        }
        shared_ptr(T* p) : px(p){}
        
    private:
        T*	px;
        long* pn;
        ··· 
    }
    
  8. ->可以一直使用,直到指向数据

  9. 迭代器类:本质是利用双向数据链表的一个额外指向其中一个元素的指针

    // 迭代器类的基本函数
        T& operator*()const{
            return (*node).data;
        }
        
        T* operator->()const{
            return &(operator*());
        }
    
    
  10. 仿函数类:

    • 标准库中的仿函数一般会继承一些奇特的基类
    // struct或class中出现operator(),则为仿函数
    template
    struct identity{
        const T& operator()(const T& x)const{
            return x;
        }
    };
    
    template
    sturct select1st{
        const typename Pair::first_type& operator()(const Pair& x)const{
            return x.first;
        }
    };
    
  11. c++中struct和class的区别

    • 可以继承,能实现多态,能包含成员函数
    • struct作为数据结构的实现体,默认的数据访问控制是public的
    • class作为对象的实现体,它默认的成员变量访问控制是private的
    • 默认的继承访问权限。struct是public的,class是private的。
  12. const是限定函数类型为常成员函数,指不能有任何改变其所属对象成员变量值的功能

  13. 函数模板的实参推导和函数推导

    class stone
    {
    public:
        stone(int w, int h, int we)
    	  :_w(w), _h(h), _weight(we)
            {      }
        bool operator<(const stone& rhs)const
        	{return _weight < rhs._weight;}
        private:
        	int _w,_h,_weight;
    };
    
    template 
    inline const T& min(const T& a, const T&){
    	return b < a ? b : a;// 进行比较时,首先找类内的符号重载
    }
    
    stone r1(2,3), r2(3,3), r3;
    r3 = min(r1, r2); // 不需要标明类型,编译器可以进行实参推导
    
  14. 成员模板

    // 将一个由对象A和对象B构成的pair拷贝进一个由类A和类B构成的pair
    class Base1{};
    class Derived1:public Base1{};
    
    class Base2{};
    class Derived2:public Base2{};
    
    template
    struct pair{
        tppedef T1 first_type;
        typedef T2 second_type;
        
        T1 first;
        T2 second;
        
        pair()
          :first(T1()), second(T2()){}
        pair(const T1& a, const T2& b)
          :first(a), second(b){}
        template// U1必须是T1的继承类
        pair(const pair& p)
          :first(p.first), second(p.second){}
    };
     
    pairp;
    pairp2(p);
    
  15. 泛化模板的特化

    template
    struct hash{};
    // 模板的特化
    template<>
    struct hash{
        size_t operator(char x)const {return x;}
    };
    template<>
    struct hash{
        size_t operator(int x)const {return x;}
    };
    template<>
    struct hash{
        size_t operator(long x)const {return x;}
    };
    // 调用示范:cout << hash()(100);
    
  16. 范围偏特化

    template <typename T>
    class C
    {
        ···
    };
    template <typename T>
    class C<T*>
    {
        ···
    };
    // 调用,模板范围的特化
    C<string> obj1;
    C<string*> obj2;// 调用第二个
    
  17. 模板模板参数

    template<typename T,
    		 template<typename T>class Container>
             >
    class XCls
    {
    private: 
        Container<T> c;
    public:
        ···
    };
    template<typename T>
    using Lst = linst<T, allocator<T>>;
    
    XCls<string,Lst> mylst2;
    
  18. variadic templates可变模板参数C++11

    void print(){}// 当一包参数全部打印完成,调用这个函数
    template <typename T, typename... Types>
    void print(const T& firstArg, const Types&...args)// 分为一个和一包pack
    {
        cout << firstArg << endl;// 每次调用打印第一个参数
        print(args...);// 递归调用,每次将一包参数中剩余的那一部分传入
    }
    sizeof...(args);// 可以标识一包pack参数的个数
    
  19. char通常是一个字节,即8位。bool通常是1位

namespace和标准库

  1. 全局属性的变量和函数或class等放到一个namespace中,不同的namespce内的变量和函数可以使用相同的名称

  2. 防止独立开发的两人或多人,在进行程序合并时出现命名冲突

  3. 父类类型指针可以指向子类的对象Base *ptr = new Derived;

  4. 程序 = 算法 + 数据结构

  5. 查看C++编译器的版本

    #include
    int main()
    {
    	std::cout<<__cplusplus;// 双下划线
    }
    
  6. auto关键字:根据返回类型推导声明的类型

    // 不用auto
    list<string> c;
    ···
    list<string>::iterator ite;
    ite = find(c.begin, c.end(), target);
    
    // 使用auto,但是新手尽量不要使用
    list<string> c;
    auto ite = find(c.begin(), c.end(), target);
    
    // error,编译器无法推导
    list<string> c;
    auto ite;
    ite = find(c.begin(), c.end(), target);
    
  7. ranged-base for

    for(变量 : 容器){// 容器是一个数据结构,编译器将容器内的元素赋值到变量中
    	statement;
    }
    
  8. 语法糖(Syntactic sugar),也译为糖衣语法:
    指计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用。通常来说使用语法糖能够增加程序的可读性,从而减少程序代码出错的机会。

  9. 引用reference和pointer指针

    int x = 0;
    int rx = 5
    int *p = &x;// 指针p指向x
    int &r = x;// 引用r代表x,实质也是指针,但是不能改变指向
    r = rx;// r仍然代表x,这是用rx给r赋值,通常x也为rx的值
    // 引用值的大小和地址与原值相同,但是这是编译器的假象
    sizeof(r) = sizeof(x)
    &r = &x
        
    // 函数的调用矛盾,两者不能同时存在
    double imag(const double &im){···}
    double imag(const double im){···}
    // 函数后面的const算函数签名的一部分,可以并存
    double imag(const double im)const{···}
    double imag(const double im){···}
    
  10. java中所有的变量来都是reference,常用于参数的传递

面向对象的理解

  1. 子类对象有父类的成分,这个也体现在实例化的内存中
  2. 析构和构造的调用顺序
    • 构造由内而外,子类的构造函数首先调用基类的默认构造函数,然后再执行自己的
    • 析构由外而内,子类的析构函数首先执行自己的,然后才调用基类的构造函数
    • 析构函数和构造函数的调用顺序是相反的
  3. 继承将数据全部继承,再实例化时需要拷贝自己的内存,而继承的函数是其调用权和重写权
  4. 函数的调用
    • 函数的静态绑定:当调用时,先call函数的内存地址,执行完成后return中断地址
    • 函数的动态绑定(虚机制):调用函数时候,需要找到对应的存放函数指针的数组位置fun[n],然后在函数指针数组元 素内的地址找到调用函数的内存地址
  5. 相同内存大小的指针组成的数组可以找到不同的内存大小的元素
  6. 通过对象来调用函数,那么这个对象的地址就是this pointer
  7. 子类的对象可以调用父类的函数,子类对象可以转换成

几个重要的关键字

  1. const对象不能调用非const函数,其他的对象均可调用其他的函数。尽量加const,因为你写的函数可能被其他人使用const对象进行调用

  2. 写时拷贝Copy-On-Write:在真正需要一个存储空间时才去声明变量(分配内存),这样会得到程序在运行时最小的内存花销

  3. 同一个成员函数的const版本和非const版本可以重载,当成员函数重载后,const对象只能调用const函数,非const对象只能调用非const函数

  4. new和delete底层调用的还是malloc和free函数

  5. new和delete的调用

    // 优先调用成员函数的new和delete,若无则调用全局的new和delete
    Foo* pf = Foo;
    delete pf;
    // 直接调用全局的new和delete
    Foo* pf = ::new Foo;
    ::delete pf;
    
  6. new[]使用时,需要在对象数组头部多4个字节标识数组大小,即调用构造和析构函数的次数

  7. new的重载第一个参数必须是size_t类型

    void *operator new(size_t size){
        return malloc(size);
    }
    
    

你可能感兴趣的:(笔记,c++,mfc,开发语言)