美文网首页
C++基础知识点整理

C++基础知识点整理

作者: 异同 | 来源:发表于2020-07-22 01:14 被阅读0次
    1. 使用new实例化出来的对象会放在堆区,一般用于复杂数据类型的实例化操作。这种方式实例化后不会自动释放空间,要使用delete进行手动释放,以避免内存泄露。
      直接实例化出来的对象会放在栈去,一般存放结构简单且空间占用较小的数据类型。使用该方式实例化后会自动释放空间。
      注意

    使用类似new进行实例化,返回的是指向这个对象的指针而不是这个对象本身。因此使用MyCalendar cal=new MyCalendar();会报错"no viable conversion from 'MyCalendar *' to 'MyCalendar'",即无法将一个MyCalendar对象的指针赋值给一个MyCalendar对象。此时应当使用MyCalendar *cal=new MyCalendar();
    另外要注意的是,变量是对象的时候用“.”访问,而变量是对象指针的时候用“->”访问。

    例:

    方式1:
    MyCalendar *cal=new MyCalendar();
    cal->print_cur_date();
    delete cal;
    
    方式2
    MyCalendar cal= MyCalendar();
    cal.print_cur_date();
    
    等同于:
    MyCalendar cal=*new MyCalendar();
    cal.print_cur_date();
    即在赋值前已经使用指针符号(*)获取到了实例对象
    

    1. 堆内存、栈内存、内存释放和野指针的问题

    代码报错内容:
    HeapSortV2(9693,0x1098dedc0) malloc: *** error for object 0x7ffeea0a77b0: pointer being freed was not allocated
    HeapSortV2(9693,0x1098dedc0) malloc: *** set a breakpoint in malloc_error_break to debug

    错误情况1

        int data[] = {1,2,3,4,5};
        int *v = data
    
        delete [] v;
        v = NULL;
    

    错误情况2

        int data[] = {1,2,3,4,5};
        int *v = new int[5];
        v = data;
    
        delete [] v;
        v = NULL;
    

    错误解释:

    首先要明白的是c++内存分配一般包括分配在堆内存和栈内存两种情况。
    栈(stack)内存基本上都是系统自动分配的,例如示例中的int data[] = {1,2,3,4,5};,即常规的变量(包括数组)赋值操作,都是在堆上进行的。
    堆(heap)内存多为用户自定义分配的,例如通过malloc申请内存,例如通过new关键字创建的实例变量。
    简单来说:栈内存由系统控制自动分配和释放内存空间,限制性较大,适用于生命周期短的变量、函数参数,一般分配的时候都是直接分配一整片连续的内存。而堆内存由程序员自己控制内存的分配和释放,灵活性强,但是由于它内存分布不是连续的,会涉及到寻址的问题,因此速度比栈内存要慢一些。而且new或malloc出来的内存如果不做释放,可能会造成内存泄露(即某些内存被占用而未做释放,导致内存资源浪费)的问题。
    通过free()或delete、delete[]等释放内存,只能用于堆内存数据。而且其实根据上面的叙述也能知道,栈内存的数据是不需要做资源释放的,系统会自行释放栈内存数据。

    那么错误情况1就能解释了
    我们创建了一个数组int data[] = {1,2,3,4,5};,这个数组是在栈内存的,会自行释放。我们用指针v指向了这个数组int *v = data,这时依然没有新开辟的空间,指针v只是在调用栈中创建的一个int指针类型的数据,他的内容是一个指向int类型数据的内存地址的值。
    那么可以看到,我们从来没有在堆内存中申请内存空间,因此delete操作是没有意义的。提示的“pointer being freed was not allocated”,意思是指针指向的那块要被清空的(堆)内存是没有被分配过数据的,说白了就是这个指针指向的是栈内存中的数组,没有指向一个堆内存,又哪里去谈什么delete/free内存呢。
    修改为如下即可,即直接让系统进行内存的释放,不需要手动干预。

        int data[] = {1,2,3,4,5};
        int *v = data
    

    情况2
    根据我们之前说的,free和delete是一定要在new出来或则是malloc出来的数据上进行的,在情况2中我们首先new了一个数组,并让指针v指向这个数组int *v = new int[5];,这时候如果我们使用delete[] v会发现是可以正常运行的,但是如果我们想情况2中又为指针v重新赋值,使他指向了位于栈中的int类型数组,那么实际上效果是和情况1等同的,这是v并没有指向一个堆内存数据,使用delete/free自然就会出错了。
    实际上当我们将代码改为以下内容后,还需要再添加一个对指针v的清空操作,否则当v指向的堆内存清空后,v指向了一个没有实际有效数据的内存区域,成了一个野指针。我们可以添加v = NULL;,或者是另v指向其他内容。

        int *v = new int[5];
        delete [] v;
    

    综合学到的内容,我们做一个实验:

    template <typename Item>
    class MaxHeap{
    private:
    Item *data;
    public:
     MaxHeap(Item data[]){
            // 直接传入一个数组,对该数组执行heapify,构建为堆
            this->data = data;
            __heapify();
        }
    ~MaxHeap(){
            delete[] data;
        }
    }
    int main() {
        int data[] = {1,2,3,4,5};
        MaxHeap<int> maxHeap = MaxHeap<int>(data);
    }
    

    上述代码会报同样的错误。分析内容可以看到,我们将创建在栈上的数组data作为构造函数的参数进行实例化,在实例化中我们让成员变量data(指针)指向了数组data的第一个元素,然后执行heapify操作。在执行完毕后我们在稀构函数中使用了delete,即犯了同上面一样的毛病,对并不是new/malloc出来的数据进行了delete/free操作。

    一个解决办法是我们new一片空间出来,然后遍历data数组的内容,挨个进行赋值:

    template <typename Item>
    class MaxHeap{
    private:
    Item *data;
    public:
     MaxHeap(Item data[], int n){
            // 直接传入一个数组,对该数组执行heapify,构建为堆
            this->data = new Item[n]
            for(int i = 0; i < n ; i++){
                this->data[i] = data[i]
            }
            __heapify();
        }
    ~MaxHeap(){
            delete[] data;
            data = NULL:
        }
    }
    int main() {
        int data[] = {1,2,3,4,5};
        MaxHeap<int> maxHeap = MaxHeap<int>(data,5);
    }
    

    当然我们可以直接使用指针对原数组进行操作,而不必再开辟新的内存空间(一般不建议这么处理)。这时我们的稀构函数其实只需要根据规范标准,把野指针处理掉就好了。

    template <typename Item>
    class MaxHeap{
    private:
    Item *data;
    public:
     MaxHeap(Item data[]){
            // 直接传入一个数组,对该数组执行heapify,构建为堆
            this->data = data
            __heapify();
        }
    ~MaxHeap(){
            data = NULL:
        }
    }
    int main() {
        int data[] = {1,2,3,4,5};
        MaxHeap<int> maxHeap = MaxHeap<int>(data,5);
    }
    

    要注意的是,不管指针指向了malloc/new的堆内存空间,还是指向了在栈内存中的普通数据类型及其数组,通过free、delete或者是系统自动回收机制将内容清空后,这个指针都是指向了无效数据,按照规范而言是一定要做重新指向或者赋值为NULL的处理的。否则在后续代码中可能无意识的仍然在使用该指针处理数据,造成数据出现篡改的情况。

    1. 友元函数
      c++类中有public和private两种成员变量及方法,如果我们在外部实例化了一个类,那么我们是无法访问到这个类的私有成员变量及私有方法的。
      如:
    #include <iostream>
    using namespace std;
    
    class A{
    private:
        int a=1;
        int b=2;
        void printPrivate(){
            cout<<"this is a private function"<<endl;
        }
    public:
        int c =3;
        int d=4;
        void printPublic(){
            cout<<"this is a public function"<<endl;
        }
    };
    
    //
    int main(){
        A classA = A();
        //这时我们无法使用A对象的私有方法printPrivate、私有成员变量a及私有成员变量b
        //error: 'a' is a private member of 'A'
        cout<<classA.a<<endl;
        //error: 'b' is a private member of 'A'
        cout<<classA.b<<endl;
        //error: 'printPrivate' is a private member of 'A'
        classA.printPrivate();
        return 0;
    }
    

    友元函数可以解决这种无法访问私有成员变量及私有方法的问题。
    可以这么理解,友元就好比是类是一个特殊的成员,它不是类所拥有的,但是又能访问类的数据,可以假想是这个类的一个“朋友(friend)”。
    这个函数的使用方法可以有多种,用的最多的是:
    在该类的内部进行声明,在该类的外部进行定义

    #include <iostream>
    using namespace std;
    
    class A{
    private:
        int a=1;
        int b=2;
        void printPrivate(){
            cout<<"this is a private function"<<endl;
        }
    public:
        int c =3;
        int d=4;
        void printPublic(){
            cout<<"this is a public function"<<endl;
        }
        //友元函数要在需要开放私有数据的那个类的内部进行声明
        //声明方法就是friend+返回值类型+函数名(参数列表)。
        //一般参数列表中可以包含这个类的本身
        friend void access_1(A);
        //也可以不包含这个类本身
        friend void access_2();
    };
    
    //友元函数要在类外部进行定义
    void access_1(A classA){
        cout<<"access_1 running"<<endl;
        cout<<classA.a<<endl;
        cout<<classA.b<<endl;
        classA.printPrivate();
    }
    //友元函数要在类外部进行定义
    void access_2(){
        cout<<"access_2 running"<<endl;
        A classA = A();
        cout<<classA.a<<endl;
        cout<<classA.b<<endl;
        classA.printPrivate();
    }
    int main(){
        A classA = A();
        access_1(classA);
        access_2();
        return 0;
    }
    
    ------------------执行结果如下-------------------
    access_1 running
    1
    2
    this is a private function
    access_2 running
    1
    2
    this is a private function
    

    除了友元函数以外,还有友元类,即friend class XXX。使用方法类似,我们在A类中声明friend class B,然后在A类外部对B类进行具体的定义,那么在B类中我们就能访问到A类(对象)的所有数据了:

    #include <iostream>
    using namespace std;
    
    class A{
    private:
        int a=1;
        int b=2;
        void printPrivate(){
            cout<<"this is a private function"<<endl;
        }
    public:
        int c =3;
        int d=4;
        void printPublic(){
            cout<<"this is a public function"<<endl;
        }
        //声明友元类B
        friend class B;
    };
    
    //定义友元类B
    class B{
    public:
        void accessToClassA(A classA){
            cout<<classA.a<<endl;
            cout<<classA.b<<endl;
            classA.printPrivate();
        }
        void accessToClassA(){
            A classA = A();
            cout<<classA.a<<endl;
            cout<<classA.b<<endl;
            classA.printPrivate();
        }
    };
    
    int main(){
        A classA = A();
        B b = B();
        b.accessToClassA();
        b.accessToClassA(classA);
        return 0;
    }
    
    ------------------执行结果如下-------------------
    1
    2
    this is a private function
    1
    2
    this is a private function
    

    还有一种使用方法,是把友元函数应用在多个类上,例如对两个不同类的某些个私有成员变量进行操作:

    #include <iostream>
    using namespace std;
    //如果下面友元方法参数列表中出现了两个类型,如B类型,
    //则必须在前面进行声明(专业术语为前序声明:forward declaration)
    class B;
    
    class A{
    private:
        int a0;
    public:
        A(int a0){
            this->a0 = a0;
        }
        //我们在A、B类外定义的sumAB方法要用到A和B两个类的数据,
        //因此在A和B两个类中都要进行友元函数的声明
        friend int sumAB(A,B);
    };
    
    class B{
    private:
        int b0;
    public:
        B(int b0){
            this->b0 = b0;
        }
        //我们在A、B类外定义的sumAB方法要用到A和B两个类的数据,
        //因此在A和B两个类中都要进行友元函数的声明
        friend int sumAB(A,B);
    };
    //在外部定义友元函数
    int sumAB(A classA,B classB){
        return classA.a0+classB.b0;
    }
    
    int main(){
        A classA = A(123);
        B classB = B(456);
        cout<<sumAB(classA,classB);
    }
    
    ------------------执行结果如下-------------------
    579
    

    当然,我们也可以在友元函数的参数列表中不加入AB两个类,而是在这个函数的定义中进行AB类的实例化处理。也就是说并不是说友元函数的参数列表中有类A类B所以我们能访问他们的私有方法,而是因为我们在类中声明了友元函数,所以我们能够通过这个友元函数来访问类的私有方法私有成员变量,这个不要搞混了。

    #include <iostream>
    using namespace std;
    
    
    class A{
    private:
        int a0;
    public:
        A(int a0){
            this->a0 = a0;
        }
        friend int sumAB();
    };
    
    class B{
    private:
        int b0;
    public:
        B(int b0){
            this->b0 = b0;
        }
        friend int sumAB();
    };
    
    //我们不指定友元函数的参数列表内容,而在函数的定义中进行对象实例化
    //但是一定要满足定义时的参数列表和声明时的参数列表是一致的,
    //因为C++重载的特性,参数列表不一致的话会认为是两个函数
    int sumAB(){
        A classA = A(123);
        B classB = B(456);
        return classA.a0+classB.b0;
    }
    
    int main(){
        cout<<sumAB();
    }
    ------------------执行结果如下-------------------
    579
    

    我们需要明确一个观点:友元函数的定义是可以出现在类内部的,也就是说我们可以在类的内部既声明友元函数,又定义友元函数,而不一定是"类的内部声明友元函数,类的外部定义友元函数"。"类的内部声明友元函数,类的外部定义友元函数"只是一种最常见的用法,而不是唯一使用方法。
    但是要注意C++不允许在类内部定义友元函数,在外部再次定义友元函数。这可不像java的什么可以对一个函数进行重写复用。友元函数的定义只能出现在一个位置。

    #include <math.h>
    #include <iostream>
    
    using namespace std;
    
    class A{
    private:
        double x;
        double sqrt_x;
    public:
        A(int x){
            this->x = x;
            sqrt_x = sqrt(x);
        }
        friend void printData(A a){
            cout<<"x = "<<a.x<<", sqrt x = "<<a.sqrt_x<<endl;
        }
    };
    
    
    int main(){
        A a = A(1024);
        printData(a);
        return 0;
    }
    

    4.在类中对 <<运算符的重载
    我们看一段代码:

    #include<math.h>
    #include <iostream>
    
    using namespace std;
    
    class A{
    private:
        double x;
        double sqrt_x;
    public:
        A(int x){
            this->x = x;
            sqrt_x = sqrt(x);
        }
        friend ostream& operator<<(ostream& os,A a){
            os<<"x = "<<a.x<<", sqrt_x = "<<a.sqrt_x<<endl;
        }
    };
    
    int main(){
        A a = A(1024);
        cout<<a;
        return 0;
    }
    
    ------------------执行结果如下-------------------
    x = 1024, sqrt_x = 32
    

    这是一个很简单的<<运算符重载,如果按照常规运算符重载的语法规则,我们只需要写成ostream& operator<<(ostream& os,A a)就行了,为什么要加friend修饰呢?

    ostream&:(返回值类型)
    operator:(固定内容)
    <<:(需要重载的运算符)
    (ostream& os,A a):(参数列表){

    这是因为在类中定义的运算符重载,在使用及书写的时候一定是类对象在运算符前面的,这样一来我们要使用<<的时候就应当写成a<<cout而不是我们所习惯的cout<<a
    也就是说,我们在前面加friend只是为了把他当做一个普通函数方法,而不是把他当做成员方法(因为成员方法的调用是一定要将类对象写在前面的),那么在使用的时候就能按照普通函数的使用方法,按参数列表的顺序进行使用,即os(类型ostream,其标准实例对象为cout)的实例对象cout在前面,运算符在中间,第二个参数在最后面。

    1. 运算符重载
      定义方法:
      返回值类型 operator需要重载的运算符(operator和运算符中间没有任何符号)(参数列表)
      如:
    #include <iostream>
    using namespace std;
    
    class A{
    private:
        int x;
        double y;
    public:
        A(int x) {
            this->x = x;
            y = -x;
        }
        //定义两个不同的A对象中,较大的是它成员变量y较大的哪一个
        bool operator>(A anotherObjectA){
            return this->y > anotherObjectA.y;
        }
    };
    
    int main(){
        A a1 = A(123);
        A a2 = A(234);
        cout<<(a1>a2)<<endl;
    }
    

    相关文章

      网友评论

          本文标题:C++基础知识点整理

          本文链接:https://www.haomeiwen.com/subject/hrxycktx.html