C++基础知识点整理

  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;
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,616评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,020评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,078评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,040评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,154评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,265评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,298评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,072评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,491评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,795评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,970评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,654评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,272评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,985评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,223评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,815评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,852评论 2 351

推荐阅读更多精彩内容

  • 抱佛脚一时爽,一直抱佛脚一直爽!这篇文章总结常见的c++面试问题~因为是抱佛脚,所以结构上没有什么逻辑...参考链...
    山幺幺阅读 713评论 0 0
  • 1.在C++ 程序中调用被C 编译器编译后的函数,为什么要加extern “C”? 答:首先,extern是C/C...
    曾令伟阅读 919评论 0 4
  • seek a firm footing in the depths.在深处寻求稳固的立足点 入门篇 变量声明及初始...
    笑笑学生阅读 513评论 0 0
  • main函数执行以前,还会执行什么代码? 答案:全局对象的构造函数会在 main 函数之前执行 static的应用...
    曾令伟阅读 1,373评论 0 2
  • 技术交流QQ群:1027579432,欢迎你的加入! 一.static关键字的作用 1.静态成员的特点 1.sta...
    CurryCoder阅读 2,750评论 3 3