C++ 类和对象初探

构造函数

初始化:被初始化的对象正在创建
赋值:被赋值的对象已经存在

构造函数为了提供初始化用的,

构造函数有:默认构造函数,拷贝构造函数

//    Test2(int a=0, int b=0): m_a(a),m_b(b) {}  推荐这样写
#include <iostream>

using namespace std;
// 同一个项目最好不要有相同的类名(防止串包)
// 默认构造函数:可以写成有参数的并且,形参写上初始值
class Test2
{
public:
    Test2()
    {
        cout << "无参构造函数" << endl;
    }
    Test2(int a)
    {
        m_a = a;
    }
    Test2(int a, int b)  // 调用有参构造函数 3种方法
    {
        m_a = a;
        m_b = b;
        cout << "有参构造函数" << endl;
    }
    // 赋值构造函数(拷贝构造函数)
    Test2(const Test2 & t2)
    {
        cout << "拷贝构造函数" << endl;
    }

    ~Test2()
    {

    }
    void printT()
    {
        cout << "普通成员函数" << endl;
    }
private:
    int m_a;
    int m_b;
};
// 调用有参构造函数 三种方法 第一种比较常用
int main()
{
    // Test2 t1;  // 调用无参构造函数
    // Test2 t1();  // 调用有参构造函数(编译能通过,使用的时候回报错)

    // 1 括号法
    Test2 t1(1, 2);   // c++编译器帮我们调用构造函数(t1和t2都是)
    t1.printT();

    // 2 = 号法
    // t2不建议使用
    Test2 t2 = (1, 2, 3, 4);  // 报错因为没有一个参数的构造函数,如果有的话就会调用一个的把4用来初始化
    Test2 t3 = 5;  // 调用一个参数的构造函数

    // 3直接调用构造函数  手动调用
    // 调用一次后面的然后转成t4
    Test2 t4 = Test2(1, 2);  // 匿名对象(匿名对象的去和留)抛砖。t4对象初始化

    t1 = t4; // 把t4拷贝给t1 拷贝构造函数
// 对象的初始化和对象的赋值是两个概念
    return 0;
}
简单分类

拷贝构造函数调用的时机

拷贝构造函数调用时机
将一个对象作为实参传递给一个非引用类型的形参
从一个返回类型为非引用的函数返回一个对象(匿名对象)
用一个类型去初始化另外一个类型 T t1 = t2
用花括号列表初始化一个数组中的元素或者一个聚合类(C++primer 266)
struct data {
int a;
string s;
};
// data test; test.a = 1; test.s = "abcd";
data test = {1, "abcd"};

注意:

//匿名对象的去和留,关键看,返回时如何接
//若返回的匿名对象,赋值给另外一个同类型的对象,那么匿名对象会被析构
//若返回的匿名对象,来初始化另外一个同类型的对象,那么匿名对象会直接转成新的对象

调用测试

用一个类型去初始化另外一个类型
Test4 t2 = t1;  // 用t1来初始化t2  拷贝构造函数
Test4 t3(t1); // 用一个对象初始化另一个对象
#include <iostream>

using namespace std;
// 同一个项目最好不要有相同的类名(防止串包)
class Test4
{
public:
    Test4(int a, int b)  // 调用有参构造函数
    {
        m_a = a;
        m_b = b;
        cout << "有参构造函数" << endl;
    }
    // 赋值构造函数(拷贝构造函数)
    Test4(const Test4 & t)
    {
        cout << "拷贝构造函数" << endl;
        m_a = t.m_a + 100;
        m_b = t.m_b + 100;
    }
    void printT()
    {
        cout << "普通成员函数" << endl;
        cout << "m_a = " << m_a << " m_b = " << m_b << endl;
    }
private:
    int m_a;
    int m_b;
};
// 构造函数是用来初始化的(拷贝..,构造函数)

// 1 拷贝构造函数;用一个对象去初始化另一个对象
int main()
{
    Test4 t0(1,2);
    Test4 t1(1,2);

//  *** 赋值=操作  不会调用构造函数 ****
//  operator=  运算符重载
    t0 = t1;   // 用t1给t0 赋值  和 初始化 是两个概念

// 第一种调用时机
    Test4 t2 = t1;  // 用t1来初始化t2  拷贝构造函数
    t2.printT();

// 第二种调用时机
    Test4 t3(t1); // 用一个对象初始化另一个对象
    t2.printT();
    return 0;
}
调用一
将一个对象作为实参传递给一个非引用类型的形参
f(类的对象作为参数); // b实参去初始化形参 会调用拷贝构造函数,
void f(类名 非饮用形参)
#include <iostream>
using namespace std;

class Location
{
public:
    Location(int _x, int _y)
    {
        x = _x;
        y = _y;
        cout << "\t构造函数" << endl;
    }
    // 拷贝构造函数 用一个对象初始化另一个对象
    Location(const Location &obj)
    {
        x = obj.x;
        y = obj.y;
        cout << "\t拷贝构造函数" << endl;
    }
    int getX() {return x;}
    int getY() {return y;}

private:
    int x;
    int y;

};
// 业务函数
void f(Location p)
{
    cout << p.getX() << endl;
}
void play()
{
    Location a(1, 2);
    Location b = a;
    cout << "\tb对象已经初始化完毕" << endl;
    f(b); // b实参去初始化形参 会调用拷贝构造函数
}
int main()
{

    play();
    return 0;
}
调用二
从一个返回类型为非引用的函数返回一个对象(匿名对象)
#include <iostream>
using namespace std;

class Location
{
public:
    Location() = default;
    Location(int _x, int _y)
    {
        x = _x;
        y = _y;
        cout << "\t构造函数" << endl;
    }
    // 拷贝构造函数 用一个对象初始化另一个对象
    Location(const Location &obj)
    {
        x = obj.x;
        y = obj.y;
        cout << "\t拷贝构造函数" << endl;
    }

    ~Location()
    {
        cout << "\t析构函数" << endl;
    }
    int getX() {return x;}
    int getY() {return y;}

private:
    int x;
    int y;

};
// 结论1: 函数的返回值是一个元素(复杂类型) 返回的是一个新的匿名对象
// (所以会调用匿名对象类的拷贝构造函数)

// 结论2: 有关匿名对象的去和留
// 如果用匿名对象 初始化 另外一个同类型的对象,匿名对象转成有名对象(objplay3函数)
// 如果匿名对象   赋值(=) 给另外一个同类型的对象, 匿名对象马上被析构(objplay4)

// 特别注意这里 在不同的编译器上不同,A应该会在返回时(就算不接收)也会调用拷贝构造函数
// 匿名对象的去和留

// 你这么写代码,c++编译器的大牛们:
// 我就给你返回一个新对象(没有名字)
Location g()  // 最好在objplay4时使用此函数
{
    Location A(1, 2);
    return A;   // 这里返回时会调用拷贝构造函数
}

void objplay2()
{
    g();
}

void objplay3()
{
    // 用匿名对象初始化m,c++编译器 直接把匿名对象转成m
    // 从无名,转为有名

    Location m = g();   // 这里不会再次调用拷贝构造函数了(匿名对象时已经调用了)
    cout << "\t匿名对象,被扶正,不会被析构掉";
    cout << m.getY() << endl;
}

void objplay4()
{
    // 用匿名对象赋值给m2后,匿名对象被析构
    Location m2(1, 2);
    m2 = g();


    cout << "\t因为用匿名对象=m2,匿名对象被析构";
    cout << m2.getY() << endl;
}


int main()
{
//  objplay2();
//  objplay3();
//  objplay4();

    return 0;
}
匿名对象

https://www.cnblogs.com/xiaokang01/p/9160475.html#_label1_4

看函数返回值的匿名对象

深浅拷贝-->后面有个static

当在类中出现指针时就要注意了,出现指针(内存重新分配)时不能使用编译器的构造函数和赋值函数了还有析构函数,要自己写

深拷贝和浅拷贝可以简单理解为:如果一个类拥有资源,当这个类的对象发生复制过程的时候,资源重新分配,这个过程就是深拷贝,反之,没有重新分配资源,就是浅拷贝。

需要写深拷贝时写成了浅拷贝带来的问题:
两个指针指向同一块内存,当一个释放了,另一个也没有了,但是你并不知道另一个也没有了,就会造成很大的错误
#include <iostream>
#include <cstring>
#include <cstdlib>
using namespace std;

class Name
{
public:
    Name (const char *mp)
    {
        len = strlen(mp);
        p = (char *)malloc(sizeof(len+1)); // 注意细节
        strcpy(p, mp);

    }

    //Name obj2 = obj1;
    // 解决方案:手工编写拷贝构造函数,使用深拷贝
    Name (const Name &obj1)
    {
        len = obj1.len;
        p  = (char *)malloc(sizeof(len+1));   // 如果不这样写,就会让两个指针指向同一块内存
        strcpy(p, obj1.p);
    }
    ~Name ()
    {
        cout << "析构函数" << endl;
        if (p != NULL)
        {
            free(p);
            p = NULL;
            len = 0;
        }
    }
private:
    char *p;
    int len;


};

// 对象析构的时候会出现,错误
// 调试存在bug
// 析构的时候会出现二次释放
//默认的拷贝构造函数,如果要对指针进行拷贝,则只是浅拷贝,拷贝过后是两个变量指向
//同一段内存,释放拷贝的obj2后,obj1的指针就是垃圾值,在释放就会出错
void objmain()
{
    Name obj1("abcde");
    Name obj2 = obj1;
    Name obj3("obj3");
    //  obj3 = obj1;  // 等号操作 c++编译器会对=进行默认的重载(同样会发生二次释放的问题)
    // 就需要手动的写 operator= 函数
}

int main()
{
    objmain();
}
例子

https://www.cnblogs.com/jin521/p/5600488.html

对象的构造和析构函数的调用顺序

构造函数:由内到外

析构函数:由外到内

#include <iostream>
using namespace std;

class A
{
public:
    A(int _a)
    {
        m_a = _a;
        cout << "a = " << m_a << endl;
        cout << "构造函数" << endl;
    }
    ~A()
    {
        cout << "析构函数" <<"a = " << m_a << endl;
    }
private:
    int m_a;
};

// 构造函数初始化列表,解决了:在B类中 组合了一个A 类对象(A类设计了构造函数)
// 根据调用函数的调用规则,写了A的构造函数必须要用,没有机会初始化A
// 新的语法规则
// constrouctor :: constructor() : m1(v1), m2(v2),m3(v3)  {}

class B
{
public:
    B(int _a, int _a2) : a1(1), a2(2),c(0)
    {

    }
    B(int _b1, int _b2, int m, int n) : a1(m), a2(n),c(0)
    {
        m_b1 = _b1;
        m_b2 = _b2;
        cout << "B的构造函数" << endl;
    }
    ~B()
    {
        cout << "B的析构函数" << endl;
    }
private:
    int m_b1;
    int m_b2;
    A a1;
    A a2;
    const int c;

};

//2 首先执行 被组合的对象的构造函数
// 如果组合对象有多个,就按照定义顺序,不按照初始化列表

// 析构函数:和构造函数 相反

// 3被组合对象的构造顺序,与定义顺序有关系,与初始化列表没有关系

// 4初始化列表用来给const属性赋值
void objplay1()
{
//    A a1(10);
//    B objB(1, 2);

    // 参数传递
    B objB2(1, 2, 3, 4);
}

int main()
{
    objplay1();
    return 0;
}
View Code
#include <iostream>
using namespace std;
// 未完
class ABCD
{
public:
    ABCD(int a, int b, int c)
    {
        this->a = a;
        this->b = b;
        this->c = c;
        cout << "ABCD的构造函数" << endl;
    }

    ~ABCD()
    {
        cout << "ABCD的析构函数" << endl;
    }

    int getA()
    {
        return this->a;
    }

private:
    int a;
    int b;
    int c;

};

class MyE
{
public:
    MyE() : abcd1(1, 2, 3), abcd2(4, 5, 6), m(100)
    {
        cout <<"MyE()构造函数" << endl;
    }
    ~MyE()
    {
        cout <<"~MyE 析构函数" << endl;
    }
    
    //    MyE m2 = my;
    MyE(const MyE & obj) : abcd1(7, 8, 9), abcd2(10,11, 12), m(100)
    {
        cout << "MyE(const MyE & obj)" << endl;
    }

    void doThing(MyE myel)
    {
        cout << myel.abcd1.getA() << endl;
    }
private:
    ABCD abcd1;  // c++编译器不知道啊如何构造abcd1
    ABCD abcd2;
    const int m;
};


int run2()
{
    MyE my;
//    MyE m2 = my;
    return 0;
}
int main()
{
    run2();
    return 0;
}
一定要看

new和delete

1 malloc free c语言中的函数
new delete c++的操作符

2 new 基础类型变量, 分配数组类型变量,分配类对象

malloc 不会调用构造函数,但是new可以
free 不会调用析构函数,但是delete可以
其他可以替换

new int;  //开辟一个存放整数的存储空间,返回一个指向该存储空间的地址(即指针)
new int(100);  //开辟一个存放整数的空间,并指定该整数的初值为100,返回一个指向该存储空间的地址
new char[10];  //开辟一个存放字符数组(包括10个元素)的空间,返回首元素的地址
new int[5][4];  //开辟一个存放二维整型数组(大小为5*4)的空间,返回首元素的地址
float *p=new float (3.14159);  //开辟一个存放单精度数的空间,并指定该实数的初值为//3.14159,将返回的该空间的地址赋给指针变量p
理论

用new分配数组空间时不能指定初值。如果由于内存不足等原因而无法正常分配空间,则new会返回一个空指针NULL,用户可以根据该指针的值判断分配空间是否成功。

new基础类型
// 分配基础类型
int main()
{
//    int *a = (int *)malloc(sizeof(int));

    int *p = new int;  // 分配基础类型
    *p = 20;
    delete p;

    int *p2  = new int(30);
    cout << "*p2 = " <<  *p2<< endl;
    delete p2;
    return 0;
}
new基础类型

new数组类型

#include <iostream>
#include <cstdlib>
#include <cstring>
using namespace std;

// 分配数组类型
int main()
{
    int *p = (int *)malloc(sizeof(int) *10);  // int a[10]
    p[0] = 1;
    free(p);

    // c++分配数组
    int *arr = new int[10];
    arr[1] = 100;
    cout << arr[1] << endl;
    delete []arr;   // 数组不要把它忘记

    char *str = new char[10];
    strcpy(str, "abcd");
    cout << str << endl;
    delete []str;
    return 0;
}
数组类型

new类 类型

#include <iostream>
#include <cstdlib>
#include <cstring>
using namespace std;

// 分配对象
class Test
{
public:
    Test(int _a)
    {
        a = _a;
        cout << "构造函数执行" << endl;
    }
    ~Test()
    {
        cout << "析构函数执行" << endl;
    }
private:
    int a;
};

// new可以调用构造函数,delete 可以调用类的析构函数
// 和malloc不能       free 不能
int main()
{
    Test *t = (Test*)malloc(sizeof(Test));
    free(t);

    Test *pT = new Test(10);
    delete pT;
    return 0;
}
类 类型

静态成员变量,静态成员函数static

静态成员变量

关键字 static 可以用于说明一个类的成员,
静态成员提供了一个同类对象的共享机制
把一个类的成员说明为 static 时,这个类无论有多少个对象被创建,这些对象共享这个 static 成员
静态成员局部于类,它不是对象成员
 
 
#include <iostream>
using namespace std;

class AA
{
public:
    void printC()
    {
        cout << "c:" << c <<  endl;
    }
    void AddC()
    {
        c++;
    }
    // 静态函数中,不能使用普通成员变量,普通成员函数
    static void getC() // 静态成员函数
    {
        cout << "c:" << c <<  endl;
        // 请问在静态函数中 可以的调用 普通成员函数么
        // 不能
        //cout << "a:" << a << endl;  // 报错:对非静态成员AA::a的非法引用
    }
private:
    int a;
    int b;
    static int c;  // 注意这个c是共用的,多个类的值相同,注意他的初始化
};

int AA::c = 10; // c的初始化

int main()
{
    AA a1, a2, a3;

    a1.printC();  // 10
    a1.AddC();  //11
    a1.printC(); // 11

    a2.printC(); // 11

    // 静态成员函数的调用方法
    a1.getC(); // 用对象点
    AA::getC();
    return 0;
}
测试
再说说static的用法(三个明显的作用一定要答出来)

1)在函数体,一个被声明为静态的变量在这一函数被调用过程中维持其值不变。
2)在模块内(但在函数体外),一个被声明为静态的变量可以被模块内所用函数访问,但不能被模块外其它函数访问。它是一个本地的全局变量。
3)在模块内,一个被声明为静态的函数只可被这一模块内的其它函数调用。那就是,这个函数被限制在声明它的模块的本地范围内使用

4)类内的static成员变量属于整个类所拥有,不能在类内进行定义,只能在类的作用域内进行定义

5)类内的static成员函数属于整个类所拥有,不能包含this指针,只能调用static成员函数

static全局变量与普通的全局变量有什么区别?static局部变量和普通局部变量有什么区别?static函数与普通函数有什么区别?

static全局变量与普通的全局变量有什么区别:static全局变量只初使化一次,防止在其他文件单元中被引用;
static局部变量和普通局部变量有什么区别:static局部变量只被初始化一次,下一次依据上一次结果值;
static函数与普通函数有什么区别:static函数在内存中只有一份,普通函数在每个被调用中维持一份拷贝
再说说static的用法(三个明显的作用一定要答出来)

http://www.cnblogs.com/33debug/p/7223869.html

 静态成员函数

静态成员函数数冠以关键字static
静态成员函数提供不依赖于类数据结构的共同操作,它没有this指针
在类外调用静态成员函数用 “类名 :: ”作限定词,或通过对象调用
 
疑难问题:静态成员函数中,不能使用普通变量。
//静态成员变量属于整个类的,分不清楚,是那个具体对象的属性。

强化训练

某商店经销一种货物。货物购进和卖出时以箱为单位,各箱的重量不一样,
因此,商店需要记录目前库存的总重量。
现在用C++模拟商店货物购进和卖出的情况。
#include "iostream"

using namespace std;

class Goods {
public :
    Goods(int w) {
        weight = w;
        total_weight += w;
    }

    ~ Goods() { total_weight -= weight; }

    int Weight() { return weight; };

    static int TotalWeight() { return total_weight; }

    Goods *next;
private :
    int weight;
    static int total_weight;
};

int  Goods::total_weight = 0;

//r尾部指针
void purchase(Goods *&f, Goods *&r, int w) {
    Goods *p = new Goods(w);
    p->next = NULL;
    if (f == NULL) f = r = p;
    else {
        r->next = p;
        r = r->next;
    } //尾部指针下移或新结点变成尾部结点
}

void sale(Goods *&f, Goods *&r) {
    if (f == NULL) {
        cout << "No any goods!\n";
        return;
    }
    Goods *q = f;
    f = f->next;
    delete q;
    cout << "saled.\n";
}

int main() {
    Goods *front = NULL, *rear = NULL;
    int w;
    int choice;
    do {
        cout << "Please choice:\n";
        cout << "Key in 1 is purchase,\nKey in 2 is sale,\nKey in 0 is over.\n";
        cin >> choice;
        switch (choice)        // 操作选择
        {
            case 1 :                           // 键入1,购进1箱货物
            {
                cout << "Input weight: ";
                cin >> w;
                purchase(front, rear, w);         // 从表尾插入1个结点
                break;
            }
            case 2 :                    // 键入2,售出1箱货物
            {
                sale(front, rear);
                break;
            }       // 从表头删除1个结点
            case 0 :
                break;                    // 键入0,结束
        }
        cout << "Now total weight is:" << Goods::TotalWeight() << endl;
    } while (choice);
    return 0;
}
static强化训练

面向对象模型初探

 

C++编译器是如何管理类、对象、类和对象之间的关系

:底层通过指针,看传入的指针,然后进行区分

C++类对象中的成员变量和成员函数是分开存储的
成员变量:
普通成员变量:存储于对象中,与struct变量有相同的内存布局和字节对齐方式
静态成员变量:存储于全局数据区中
成员函数:存储于代码段中。
问题出来了:很多对象共用一块代码?代码是如何区分具体对象的那?
#include <iostream>
using namespace std;
// 注意上一个视频的 字节占用数
class Test
{
public:
    Test(int _a, int _b)// ==>  Test(Test *this, int a, int b)
    {
        this->a = _a;
        this->b = _b;
    }
    void print17()
    {
        cout << "a:" << a << endl;
        cout << "b:" << this->b << endl;
    }
    // 1 const写在什么地方  没有关系
    // 2 const修饰的是谁
    // 2-3const 修饰的是this指针所指向的内存空间,修饰的是this指针


    // void const OpVar(int a, int b)这两个一样 不过一般是放在后面
    void OpVar(int a, int b) const // ==>void OpVar(const Test *this, int a, int b)
    // 更精确点是void OpVar(const Test* cosnt this, int a, int b)
    {
        a = 100;
//        this->a = 100;  // 报错
//        this->b = 100;
        cout << "b:" << this->b << endl;
    }
//     void OpVar(int a, int b) ==>void OpVar(Test* cosnt this, int a, int b)
private:
    int a;
    int b;
};
int main()
{
    Test t1(1, 2);
    t1.print17();  // ==> printT(&t1);
    return 0;
}
看了这个例子秒懂
#include <iostream>
using namespace std;
class Test18
{
public:
    int a;
    int b;
    Test18(int a=0, int b=0)
    {
        this->a = a;
        this->b = b;
    }
public:
    void print18()
    {
        cout << "a:" << a << "\tb:" << b << endl;
    }

    // 成员函数法
    //   t3 = t1.TestAdd2(t2);
    Test18 TestAdd2(Test18 &t2)
    {
        // 产生一个匿名对象
        Test18 temp(this->a + t2.a, this->b + t2.b);
        return temp;
    }


    //t1.TestAdd3(t2);
    // 这里返回的是t1
    Test18& TestAdd3(Test18 &t2)
    {
        this->a += t2.a;
        this->b += t2.b;

        return *this;  // this 就是t1的地址,*t1又回到t1的元素
    }
    ~Test18()
    {
        cout << "析构函数自动被调用";
        cout << "a:" << a << "\tb:" << b << endl;;

    }

private:

};

// 全局函数方法
Test18 TestAdd(Test18 &t1, Test18 &t2)
{
    Test18 temp(t1.a+t2.a, t1.b + t2.b);
    return temp;
}

int main1802()
{
    Test18 t1(1, 2);
    Test18 t2(3, 4);

    // t1 = t1+t2;
    t1.TestAdd3(t2);
    t1.print18();
    return 0;
}

int main1801()
{
    Test18 t1(1, 2);
    Test18 t2(3, 4);
    Test18 t3;

    // t1 + t2;
//     全局函数法
    t3 = TestAdd(t1, t2);
    t3.print18();

//     成员函数法
// 先把测试案例写出来 重点是思路
    {
        // 匿名对象的两种接法
        Test18 t4 = t1.TestAdd2(t2); // 匿名对象转正 转给4
        t4.print18();

        Test18 t5;
        t5 = t1.TestAdd2(t2);  // 匿名对象 复制给5 最好用这个
        // t5接收后 前面需要调用析构函数,而t4不用
        t5.print18();
    }
    return 0;
}
全局函数和成员函数

友元函数和友元类

友元函数:在成员函数的前面添加关键字friend ,破坏了 类的封装性, 但是在重载 << 时要用到

友元类:了解就行

#include <iostream>
using namespace std;

class A
{
public:
    friend class B; // B类是A类的好朋友,就是说在B类 可以修改A类的  私有属性,私有函数
    // 1声明的位置 和public和private无关系
    friend void modify(A &p, int _a); // 2 modify函数是类A的朋友

    A(int a = 0, int b = 0)
    {
        this->a = a;
        this->b = b;
    }
    void printA()
    {
        cout << "a = " << a << "\tb = " <<  b << endl;
    }
private:
    int a;
    int b;
};

class B
{
public:
    void Set(int b)
    {
        Aobject.b = b;
    }
    void printB()
    {
        cout << "Aobject.b = " << Aobject.b << endl;
    }
private:
    A Aobject;
};

// 不加友元函数声明就会报错
void modify(A &p, int _a)
{
//    p.a = 100;
    p.a = _a;
}
// 友元类 先了解, 友元函数 要会
// 为什么要设计友元函数
int main()
{
    B b1;
    b1.Set(200);
    b1.printB();
    return 0;
}
int main2101()
{
    A a1(1, 2);
    a1.printA();

    modify(a1, 300);
    a1.printA();
    return 0;
}
了解即可


猜你喜欢

转载自www.cnblogs.com/xiaokang01/p/9163833.html