NDKDNK 开发c/c++

Android NDK开发之旅21--C++--类

2017-03-26  本文已影响399人  小楠总

前言

C++是面向对象的编程语言,因此有类的概念。

类的定义是在头文件,实现在源文件中,这里为了方便,都写在源文件中。

构造函数

  1. 系统会自动创建默认的无惨构造函数,但是一旦提供了有参的,默认的就会去掉了。

  2. 构造有两种写法。

    class Teacher
    {
    public:
    Teacher(){
    cout << "无参构造函数" << endl;
    }
    Teacher(char* name, int age){
    cout << "有参构造函数" << endl;
    this->name = name;
    this->age = age;
    }

    private:
    char* name;
    int age;
    };

void main(){
    Teacher t1 = Teacher("nan",20);
    Teacher t2("lu", 18);
    system("pause");
}

构造函数的初始化属性列表

现在有Student类,里面有Teacher的私有成员两个。Student构造的时候需要初始化Teacher,但是又不能直接访问Teacher类的私有属性。

因此只能通过初始化列表的方式来调用。相当于Java利用super(...)调用父类的构造函数初始化父类。

class Teacher
{
public:
    Teacher(char* name, int age){
        this->name = name;
        this->age = age;
    }
    Teacher(){
    }
private:
    char* name;
    int age;
};

class Student{
public:
    Student(int id, char* t1_name, int t1_age, char*t2_name, int t2_age)
        :t1(t1_name, t1_age), t2(t2_name, t2_age)
    {
        //t1.name = t1_name;不可以这样赋值,因为Teacher的私有属性不可以访问
    }

private:
    int id;
    Teacher t1;
    Teacher t2;
};

void main(){
    Student s(1, "wu", 20, "li", 18);
    system("pause");
}

析构函数

析构函数:当对象要被系统释放时(例如函数栈退出的时候),析构函数被调用
作用:善后处理,例如释放动态分配的内存。

class Teacher
{
public:
    Teacher(){
        cout << "无参构造函数" << endl;
        name = (char*)malloc(sizeof(char)* 100);
        strcpy(name, "lu");
        age = 18;
    }
    ~Teacher(){
        cout << "析构函数" << endl;
        free(name);
    }

private:
    char* name;
    int age;
};

void fun(){
    Teacher t;
}

void main(){
    fun();
    system("pause");
}

拷贝构造函数

  1. 系统默认的拷贝构造函数就是拷贝值的,跟下面的写法一样。

  2. 拷贝构造函数被调用的场景有(实质上都是第一种的变种),有印象即可:

     一、声明时赋值(只有声明的时候才会调用)
     二、作为参数传入,实参给形参赋值(引用传值不会调用,因为指向的都是同一片内存,不存在拷贝问题)
     三、作为函数返回值返回,给变量初始化赋值
    

例子如下:

class Teacher
{
public:
    Teacher(char* name, int age){
        cout << "有参构造函数" << endl;
        this->name = name;
        this->age = age;
    }
    Teacher(const Teacher &obj){
        cout << "拷贝构造函数" << endl;
        this->name = obj.name;
        this->age = obj.age;
    }

private:
    char* name;
    int age;
};

Teacher fun(Teacher t){
    return t;
}

void main(){
    Teacher t1 = Teacher("nan", 20);
    Teacher t2 = t1;
    Teacher t3 = fun(t1);

    system("pause");
}

浅拷贝

默认的拷贝构造函数实现是浅拷贝,即值拷贝,如下所示:

class Teacher
{
public:
    Teacher(){
        this->name = (char*)malloc(sizeof(char)* 100);
        strcpy(name, "lu");
        this->age = age;
    }
    ~Teacher(){
        cout << "析构函数" << endl;
        free(name);
    }
    Teacher(const Teacher &obj){
        cout << "拷贝构造函数" << endl;
        this->name = obj.name;
        this->age = obj.age;
    }

private:
    char* name;
    int age;
};

void fun(){
    Teacher t1;
    Teacher t2 = t1;
}

void main(){
    fun();
    system("pause");
}

值拷贝带来的问题:

如果有动态内存分配的时候,如果单纯是值拷贝,析构的时候就会析构两次。

例如代码中的char* name,t1、t2都指向了同一个地址,那么析构的时候就会free两次,因为一个内存地址不能释放两次,因此会触发异常中断。

如下图所示:

浅拷贝的问题.png

深拷贝

重写覆盖默认的浅拷贝,自己写一个深拷贝:

t2的name是新分配的内存,因此t1、t2指向的是两片不同的内存,因此析构的时候分别释放,互不干扰,解决了浅拷贝的问题。

class Teacher
{
public:
    Teacher(){
        this->name = (char*)malloc(sizeof(char)* 100);
        strcpy(name, "lu");
        this->age = age;
    }
    ~Teacher(){
        cout << "析构函数" << endl;
        free(name);
    }
    Teacher(const Teacher &obj){
        cout << "拷贝构造函数" << endl;
        int len = strlen(obj.name);
        this->name = (char*)malloc(sizeof(char)* (len + 1));//+1是因为结束符
        strcpy(this->name, obj.name);
        this->age = obj.age;
    }

private:
    char* name;
    int age;
};

void fun(){
    Teacher t1;
    Teacher t2 = t1;
}

void main(){
    fun();
    system("pause");
}

如下图所示:

深浅拷贝的区别.png

动态内存分配

  1. C++中使用new和delete进行动态内存分配,这时候构造和析构会调用。
  2. C中使用malloc和free进行动态内存分配,构造和析构不会调用。

例子:

void main(){
    cout << "C++中使用new和delete进行动态内存分配" << endl;
    Teacher* t1 = new Teacher("wu", 20);
    delete t1;

    cout << "C中使用malloc和free进行动态内存分配" << endl;
    Teacher* t2 = (Teacher*)malloc(sizeof(Teacher));
    free(t2);
    system("pause");
}

数组的话,delete的时候需要加上[]:

int* arr1 = (int*)malloc(sizeof(int)* 10);
arr1[0] = 1;
free(arr1);

int* arr2 = new int[10];
arr2[0] = 1;
delete[] arr2;

类的静态属性和函数

  1. 类的静态属性必须放在全局的地方初始化。
  2. 非静态、静态函数可以访问静态属性,但是静态函数只能访问静态属性。(跟Java一样)
  3. 静态的属性和方法可以直接通过 类名:: 来访问(如果是public的话)。也可以通过 对象:: 来访问。

例子:

class Test
{
public:
    void m1(){
        count++;
    }
    static void m2(){
        count++;
    }
private:
    static int count;
};

//静态属性初始化
int Test::count = 0;

void main(){
    //Test::count
    Test::m2();

    system("pause");
}

类的大小(sizeof)

C++的内存分区:

栈
堆
全局(静态、全局)
常量区(字符串)
程序代码区

结构体有字节对齐的概念,那么类的普通属性与结构体相同的内存布局。

例如下面的结构体大小是32 = 4 x 8:

struct C{
public:
    int i;//8个字节(由于字节对齐,与double一样为8个字节)
    int j;//8个字节
    int k;//8个字节
    double x;//8个字节
    void(*test)();//指针是4个字节,但是存放在代码区
};

例如下面的类的大小是24(这里没搞懂,先放一放,以后回来再补)

class A{
public:
    int i;//8个字节(由于字节对齐,与double一样为8个字节)
    int j;//8个字节
    int k;//8个字节
    double t;//8个字节

    static int m;//4个字节,但是存在于全局静态区
    void test(){//4个字节,相当于函数指针的写法,但是存放在代码区
        cout << "打印" << endl;
    }
};

类的this指针常函数

  1. const写在函数后面。
  2. 此时的const修饰的是this指针,this指针本类就是指针常量,不能修改值。
  3. 此处增加了const的修饰之后,tihs指针更加是一个常量指针,指针指向的值不能修改。

例子:

class Teacher{
private:
    char* name;
    int age;
public:
    Teacher(char* name, int age){
        this->name = name;
        this->age = age;
    }
    //常函数,修饰的是this
    //既不能改变指针的值,又不能改变指针指向的内容
    //const Teacher* const this
    void myprint() const{
        printf("%#x\n", this);
        //改变属性的值
        //this->name = "yuehang";
        //改变this指针的值
        //this = (Teacher*)0x00009;
        cout << this->name << "," << this->age << endl;
    }
};

this,当前对象的指针

C++函数是共享的(多个对象共享代码区中的一个函数),必须要有能够标识当前对象是谁的办法,那就是this指针。

函数共享.png

例如我们用结构体去模拟类的时候,就需要自定义this指针了。所以说,在JNI开发的时候,JniEnv在C语音里面是二级指针(结构体实现,C语言中结构体没有tihs指针),在C++中是一级指针(类实现,类有this指针)

C++编译器对普通成员函数的内部处理.png

拓展:

Java内存分区:

  1. JVM Stack(基本数据类型、对象引用)
  2. Native Method Stack(本地方法栈)
  3. 方法区
  4. 程序计数区
  5. 直接内存

热修复的基本概念:拿到错误的函数的指针,指向正确的函数的指针。(两个函数都已经加载的情况下)

友元函数、友元函数

主要作用:类、类与函之间的数据共享。比如类已经写好了,不想去改动类的结构(比如访问权限,那么最好的办法就是使用友元)

在友元函数中可以访问类的私有的属性。

例如,fun是类A的友元函数,因此fun中可以访问A的私有属性。

class Test{
    friend void fun(Test &t, int i);//友元函数声明
public:
    Test(int i){
        this->i = i;
    }
private:
    int i;
};

//友元函数实现,可以是全局的,也可以是其他类的
void fun(Test &t, int i){
    t.i = i;
}

void main(){
    Test t(10);
    fun(t, 20);
    system("pause");
}

同理,B是A的友元类,因此B可以访问A的私有属性:

class A{
    friend class B;
private:
    int i;
};

class B
{
public:
    void test(){
        a.i = 20;
    }
private:
    A a;
    int i;
};

拓展:在Java(Java是C++写的嘛)中Class就可以理解为Object的“友元类”,Class类就可以访问Object的任何成员。

运算符重载

运算符重载的本质还是函数的调用。

现在有一个类:

class Point
{
public:
    Point(int x, int y){
        this->x = x;
        this->y = y;
    }

public:
    int x;
    int y;
};

需要重载“+”:

Point operator+(Point &p1, Point &p2){
    return Point(p1.x + p2.x, p2.y + p2.y);
}

注意,如果Point的x、y属性都是私有的话,就需要用到友元函数了,例如类已经写好了,不想破坏类的结构:

class Point
{
    friend Point operator+(Point &p1, Point &p2);
public:
    Point(int x, int y){
        this->x = x;
        this->y = y;
    }

private:
    int x;
    int y;
};

Point operator+(Point &p1, Point &p2){
    return Point(p1.x + p2.x, p2.y + p2.y);
}

如果是在类内部的运算符重载,因为有this指针,重载的时候可以省略一个参数,类可以直接访问自己的私有成员:

class Point
{
public:
    Point(int x, int y){
        this->x = x;
        this->y = y;
    }

    Point operator+(Point &p){
        return Point(this->x + p.x, this->y + p.y);
    }

private:
    int x;
    int y;
};

如果觉得我的文字对你有所帮助的话,欢迎关注我的公众号:

公众号:Android开发进阶

我的群欢迎大家进来探讨各种技术与非技术的话题,有兴趣的朋友们加我私人微信huannan88,我拉你进群交(♂)流(♀)

上一篇 下一篇

猜你喜欢

热点阅读