前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >3.类和对象

3.类和对象

作者头像
小飞侠xp
发布2018-12-25 15:18:57
5090
发布2018-12-25 15:18:57
举报
封装

类的基本思想:数据抽象和封装 数据抽象是一种依赖接口和实现分离的编程技术 接口:类的用户所能执行的操作 实现:类的数据成员、接口函数的实现及其他私有函数的实现 封装:实现了类的接口和实现分离 封装后的类隐藏了实现细节; 类的用户只能使用接口而无法访问实现部分。 面向对象三大特性:封装、继承、多态

访问控制

struct结构体→结构体变量 class类→对象

  1. 数据与行为不分离(成员变量,成员函数)
  2. 权限控制: 类内开放,类外控制 保证数据的完整、正确性
  3. 成员函数,调用内部变量不需要传参 访问说明符: public:公共的 protected:保护的 private:私有的 C++中class和struct没有本质区别,只是默认权不同。
C语言版
代码语言:javascript
复制
///c格式,struct
#include <stdio.h>
typedef struct _Date {
    int y, m, d;
}Date;
int isValid(Date *pd) {
    if (pd->y > 0 && pd->y < 9999 &&
        pd->m >0 && pd->m < 13 && 
        pd->d>0 && pd->d < 32)
        return 1;
    return 0;
}
void init(Date *pd) {
    while (1) {
        printf("请输入年 月 日:\n");
        scanf("%d%d%d", &pd->y, &pd->m, &pd->d);
        if (isValid(pd)) break;
        printf("输入格式错误,重新输入!\n");
    }
}
void print(Date *pd) {
    printf("%d-%d-%d\n", pd->y, pd->m, pd->d);
}
int main()
{
    Date d1;
    init(&d1);
    print(&d1);
    return 0;
}

C语言的问题:结构体中所有东西都是可见和操作的

C++版
代码语言:javascript
复制
#include <iostream>
using namespace std;
class Date {
public:
    void init() {
        while (1) {
            cout << "请输入年 月 日:" << endl;
            cin >> y >> m >> d;
            if (isValid()) break;
            cout << "输入错误,重新输入!" << endl;
        }
    }
    void print() {
        cout << y << "-" << m << "-" << d << endl;
    }
private:
    bool isValid() {
        if (y > 0 && y < 9999 && m>0 &&
            m < 13 && d>0 && d < 32)
            return true;
        return false;
    }
    int y, m, d;
};
int main() {
    Date d1;
    d1.init();
    d1.print();
    return 0;
}
栈类
代码语言:javascript
复制
#include <iostream>
using namespace std;
class Stack {
public:
    bool isEmpty()const { return topidx == 0; }
    bool isFull()const { return topidx == size; }
    void init(int len = 1024) {
        ps = new int[len];
        size = len;
        topidx = 0;
    }
    void destroy() {
        if (ps) delete[] ps;
        ps = NULL;
    }
    int top()const {
        return ps[topidx - 1];
    }
    void push(int data) {
        ps[topidx++] = data;
    }
    void pop() {
        topidx--;
    }
private:
    int *ps;
    int topidx;
    int size;
};
int main() {
    Stack S;
    S.init();
    for (int i = 0; i < 5; i++) {
        if (!S.isFull())
            S.push(i);
    }
    while (!S.isEmpty()) {
        cout << S.top() << " ";
        S.pop();
    }
    //S.topidx = 10; //错误,无法修改
    S.destroy();
    return 0;
}

构造函数与析构函数

构造函数:每个类都定义了它的对象被初始化的方式,类通过一个或多个特殊的成员函数来控制其对象的初始化。(生成对象时自动调用) 析构函数:释放对象使用的资源。(对象销毁时自动调用)

构造函数:与类名相同,无返回,可以有参数 析构函数:~与类名相同,无参数,无返回

代码语言:javascript
复制
#include <iostream>
using namespace std;
class Stack {
public:
    Stack(int len = 1024) {
        ps = new int[len];
        size = len;
        topidx = 0;
        cout << "构造函数run..." << endl;
    }
    ~Stack() {
        if (ps) delete[] ps;
        ps = NULL;
        cout << "析构函数run..." << endl;
    }
    bool isEmpty()const { return topidx == 0; }
    bool isFull()const { return topidx == size; }
    int top()const {
        return ps[topidx - 1];
    }
    void push(int data) {
        ps[topidx++] = data;
    }
    void pop() {
        topidx--;
    }
private:
    int *ps;
    int topidx;
    int size;
};
int main() {
    Stack S;
    //S.init(); //不再需要,被构造函数替代了
    for (int i = 0; i < 5; i++) {
        if (!S.isFull())
            S.push(i);
    }
    while (!S.isEmpty()) {
        cout << S.top() << " ";
        S.pop();
    }
    cout << endl;
    //S.destroy(); //不再需要,被析构函数替代了
    return 0;
}
构造函数初步
  1. 可以有参数,有默认参数,可以重载
  2. 若未提供构造参数,系统默认生成一个无参空构造函数;若提供,则不再默认生成无参空构造函数

类名a; //调用无参构造函数[不能写成类名a(),编译器会认为是函数声明] 类名a();//调用有参构造函数,a{xx}也可以 通过new在堆空间创建对象,同样会自动调用构造函数

代码语言:javascript
复制
#include <iostream>
using namespace std;
class Stack {
public:
    Stack() { //无参构造函数
        ps = new int[1024];
        size = 1024;
        topidx = 0;
        cout << "Stack() run" << endl;
    }
    Stack(int len) { //带参构造函数
        ps = new int[len];
        size = len;
        topidx = 0;
        cout << "Stack(int len) run" << endl;
    }

    /*Stack(int len = 1024) {
    ps = new int[len];
    size = len;
    topidx = 0;
    }*/
    ~Stack() {
        if (ps) delete[] ps;
        ps = NULL;
        cout << "析构函数run..." << endl;
    }
    bool isEmpty()const { return topidx == 0; }
    bool isFull()const { return topidx == size; }
    int top()const {
        return ps[topidx - 1];
    }
    void push(int data) {
        ps[topidx++] = data;
    }
    void pop() {
        topidx--;
    }
private:
    int *ps;
    int topidx;
    int size;
};
int main() {
    Stack S1;      //调用无参构造 不能写 Stack S1();
    Stack S2(100); //调用带参构造
    Stack S3{ 10 };//调用带参构造
    Stack *p1 = new Stack; //无参构造
    Stack *p2 = new Stack(10);//带参构造
    Stack *p3 = new Stack{ 10 };//带参构造
                                //对照:
    int a1; //不能写 int a1(); 这是函数声明
    int a2(10);
    int a3{ 10 };
    int *pa1 = new int;
    int *pa2 = new int(10);
    int *pa3 = new int{ 20 };
    return 0;
}
mystring类的构造函数

模范标准库的string类: string s1; // 无参构造函数 string s2("abc");//有参构造函数

代码语言:javascript
复制
#include<iostream>
#include<string>
#include<string.h>
using namespace std;
class mystring{
public:
    mystring(){
        ps = new char[1];
        ps[0] = '\0';
    }
    mystring(const char *str){
        int len = strlen(str) + 1;
        ps = new char[len];
        strcpy(ps,str);
    }
        /*
    myString(const char *str = NULL) {
    if (str == NULL) {
    ps = new char[1];
    ps[0] = '\0';
    }
    else {
    int len = strlen(str) + 1;
    ps = new char[len];
    strcpy(ps, str);
    }
    }*/
    ~mystring(){
        delete[] ps;
    }
    const char* c_str() const{
        return ps;
    }
private:
    char *ps;
};
int main(){
    string s1;
    string s2("abc");
    cout<<"-"<<s1.c_str()<<"-"<<endl;
    cout<<s2.c_str()<<endl;
    mystring ms1;
    mystring ms2("abc");
   cout<<"-"<<ms1.c_str()<<"-"<<endl;
    cout<<ms2.c_str()<<endl;
}
析构函数初步

析构函数:释放对象使用的资源(对象销毁时自动调用) 1.无参,无返回(不可重载) 2.若为提供,系统默认生成一个空的析构函数 通过delete销毁堆空间上的对象,同样会自动调用析构。 设计原则:自己申请的资源,自己负责释放

代码语言:javascript
复制
///析构函数初步 c代码
struct Stu {
    char *name;
    int age;
};
int main() {
    //申请st1的内存
    struct Stu *st1 = 
       (struct Stu*)malloc(sizeof(struct Stu));
    //申请st1中name的内存
    st1->name = (char*)malloc(sizeof(char)*20);
    //释放st1中name的内存
    free(st1->name);
    //释放st1本身的内存
    free(st1);
    return 0;
}

C代码要逐级分别释放各自申请的资源

代码语言:javascript
复制
///C++代码
class Stu {
public:
    Stu() {
        name = new char[20]; 
        age = 10; 
    }
    ~Stu() {
        if (name) delete[] name; 
    }
private:
    char *name;
    int age;
};
int main() {
    //申请st1的内存
    Stu *st1 = new Stu;
    delete st1;
    return 0;
}
构造与析构的次序

1.多个对象,按次序构造,析构次序相反 2.类中有成员变量也是类对象的时候,先运行成员类的构造函数,在运行本类的构造函数。析构次序与构造次序相反。 3.注意类中成员变量是类的指针类型的话,不会调用构造函数

代码语言:javascript
复制
#include <iostream>
#include <stdio.h>
using namespace std;
class A {
public:
    A(int i = 0) {
        num = i;
        cout << "A()" << num << endl;
    }
    ~A() { cout << "~A()" << num << endl;   }
private:
    int num;
};
class B {
public:
    B() { cout << "B()" << endl; }
    ~B() { cout << "~B()" << endl; }
};
int main() {
    A a1;
    A a2(1);
    B b;
    A *pa = new A[2]{ 2,3 }; //连续构造2次
    delete[] pa;             //连续析构2次
    return 0;
}
代码语言:javascript
复制
#include <iostream>
#include <string>
#include <string.h>
using namespace std;
class myString {
public:
    myString(const char *str = NULL) {
        if (str == NULL) {
            ps = new char[1];
            ps[0] = '\0';
        }
        else {
            int len = strlen(str) + 1;
            ps = new char[len];
            strcpy(ps, str);
        }
        cout << "myString构造" << endl;
    }
    ~myString() {
        if (ps) delete[] ps;
        cout << "myString析构" << endl;
    }
private:
    char *ps;
};
class Stu {
public:
    Stu() { cout << "Stu构造" << endl;    }
    ~Stu() { cout << "Stu析构" << endl; } 
private:
    myString name;
    int age;
};
int main() {
    Stu st1;
    return 0;
}
代码语言:javascript
复制
#include <iostream>
using namespace std;
class A {
public:
    A() { cout << "A()" << endl; }
    ~A() { cout << "~A()" << endl; }
};
void fun() { A a2; }
int main() {
    {
        A a1;
    }
    //到这里,a1已经析构了。
    fun();
    //到这里,fun中的a2已经析构了。
    cout << "----------" << endl;
    int i = 0;
    //注意a3的作用域
    for (A a3; i < 3; i++) {
        cout << i << endl;
    }
    cout << "----------" << endl;
    //注意a4的作用域
    for (int j = 0; j < 3; j++) {
        A a4; //每次循环都会构造
        cout << j << endl;
    }//while循环也是如此
    cout << "----------" << endl;
    A *pa = new A; //构造
    delete pa; //调用delete析构
    return 0;
}
  1. 栈空间中的对象脱离作用域时,析构
  2. 堆空间中的对象delete时,析构
类文件写法

通常将一个类分为2个文件:类的声明写在类名.h;类的实现写在类名.cpp 一个类就是一个作用域 在类的外部定义成员函数时,返回值类型 类名::函数名(参数)

代码语言:javascript
复制
///myString.h
#ifndef MYSTRING_H
#define MYSTRING_H
#include <iostream>
class myString {
public:
    myString(const char *str = NULL);
    ~myString();
    const char* c_str()const;
private:
    char *ps;
};
#endif // !MYSTRING_H


///调用
#include <iostream>
#include "myString.h"
using namespace std;

int main() {
    myString s1;
    return 0;
}
代码语言:javascript
复制
///myString.cpp
#include <string.h>
#include "myString.h"
//注意,默认参数要写在函数声明中
myString::myString(const char *str) {
    if (str == NULL) {
        ps = new char[1];
        ps[0] = '\0';
    }
    else {
        int len = strlen(str) + 1;
        ps = new char[len];
        strcpy(ps, str);
    }
}
myString::~myString() {
    delete[] ps;
}
const char* myString::c_str()const {
    return ps;
}
对象的内存

类→对象,模具→产品 创建对象时:只有成员变量开辟内存,没有成员变量是,占用一个字节。成员函数并不占用对象的空间。 class 中的成员变量和C中的struct一样,要对齐、补齐。

代码语言:javascript
复制
#include <iostream>
using namespace std;
class A1 {};
class A2 { void fun() {} };
class A3 { int num; void fun() {} };
class A4 { int num; char name[6]; };
int main() {
    cout << sizeof(A1) << endl; //1
    cout << sizeof(A2) << endl; //1
    cout << sizeof(A3) << endl; //4
    cout << sizeof(A4) << endl; //12
    return 0;
}
代码语言:javascript
复制
#include <iostream>
#include <string.h>
using namespace std;

class myString {
public:
    myString(const char *str = NULL);
    ~myString();
private:
    char *ps; int size;
};
myString::myString(const char *str) {
    if (str == NULL) {
        ps = new char[1];
        ps[0] = '\0';
    }
    else {
        int len = strlen(str) + 1;
        ps = new char[len];
        strcpy(ps, str);
    }
}
myString::~myString() {
    delete[] ps;
    cout << "~myString()" << endl;
}

class Person {
public:
    Person() { age = 20; p_fm = NULL; }
    void set_fm(){
        p_fm = new myString[2];
    }
    ~Person() { 
        cout << "~Person()" << endl;
        delete[] p_fm; 
        cout << "=========" << endl;
    }
private:
    int age;        //年龄
    myString name;  //自己的名字
    myString *p_fm; //父母的名字
};

int main() {
    Person p1;
    p1.set_fm();
    return 0;
}

在内存怎么存储: person P1 在栈里面:

this 指针

This 指针可以认为是顶层const,不能修改 为了区分num,对于外层的num用A::num表示,当然也可以用this->num

代码语言:javascript
复制
#include <iostream>
#include <string.h>
using namespace std;
class A {
public:
    A(int num) { A::num = num; }
    A(double num) { this->num = num; }
    int num; 
};
int main() {
    A a1(20);
    cout << a1.num << endl; //20
    A a2(1.2);
    cout << a2.num << endl; //1
    return 0;
}

对象的内存和函数没有关系,怎么区分是哪一个num? show函数是如何知道返回的是a1还是a2的num? 可想像为:int A::show( A* const this); 调用a1.show(&a1); a2.show(&a2); 成员函数隐式地传递了一个当前对象的指针参数 this指针:指向的是本对象的地址

代码语言:javascript
复制
#include <iostream>
#include <string.h>
using namespace std;
class A {
public:
    A(int num) { this->num = num; }
    int show()const { return num; }
private:
    int num; 
};
int main() {
    A a1(20);
    cout << a1.show() << endl; //20
    A a2(1);
    cout << a2.show() << endl; //1
    return 0;
}
构造函数初始值列表

有些类中,初始化和赋值的区别事关低层效率的问题,初始化的先后顺序和成员变量出现的先后次序一致

代码语言:javascript
复制
#include <iostream>
#include <string.h>
using namespace std;
class A {
public:
    A(int n, double f) { num = n; fd = f; }
private:
    int num;
    double fd;
};
class B {
public:
                  //构造函数的初始值列表
    B(int n, double f) :num(n), fd(f) {}
private:
    int num;
    double fd;
};
int main() {
    A a(10, 1.2);
    B b(10, 1.2);
    return 0;
}

A类在创建对象时,先分别对num和fd调用默认初始化; 然后再通过赋值语句给num和fd赋值。 B类在创建对象时,直接初始化num和fd. A的流程:int num ; num = 10 ; B的流程: int num(10); 假如是类成员变量:A--> myString ma; ms = "abc"; B--> myString ms("abc");

代码语言:javascript
复制
#include <iostream>
#include <string.h>
using namespace std;
class B {
public:
    B(double f) :fd(f), num(fd) {}
    int get_num()const { return num; }
private:
    int num;
    double fd;
};
int main() {
    B b(1.2);
    cout << b.get_num() << endl;
    //没有得到预期中的 1
    return 0;
}

初始值列表对成员变量的初始化先后次序,与成员变量在类中出现的先后次序一致。 尝试用排序靠后的成员变量来初始化前面的成员变量,会得到未知的结果。

拷贝构造和赋值运算符重载

拷贝构造函数: class 类名{ 类名(const 类名 & another); }

  1. 系统提供默认的拷贝构造,若自己提供,则不复存在。
  2. 默认拷贝构造是等位拷贝,也就是所谓的浅拷贝。
  3. 要实现深拷贝,必须要自己实现

赋值运算符重载: 类名{ 类名& operator = (const 类名 & 源对象) ....; return *this; }与拷贝构造类似

代码语言:javascript
复制
string s1("abc");  // 直接初始化
string s2("1234");
string s3(s2);    //  直接初始化
string s4 = s2;   // 拷贝初始化
string s5;
s5 = s2;         // 赋值操作
默认的拷贝构造(浅拷贝)
代码语言:javascript
复制
class A {
public:
    A(int n = 0) : num(n){}
    A(const A &other) :num(other.num){}
private:
    int num;
}
int main(){
    A a1;
    A a2(a1);    //直接初始化
    A a3 = a1; // 拷贝初始化
    const A a4;
    A a5 = a4; //参数没有const,就错误
    return 0;
}
运算符重载
代码语言:javascript
复制
#include <iostream>
#include <string>
using namespace std;

class A {
public:
    A(int n = 0) :num(n) {}
    A(const A &other) :num(other.num){  }
    A& operator=(const A &other) {
        num = other.num;
        return *this;
    }
private:
    int num; 
};
int main() {
    A a1;
    A a2;
    a2 = a1; //赋值
    return 0;
}
浅拷贝

浅拷贝可能会造成内存泄漏、重析构

代码语言:javascript
复制
myString(const char * str = NULL){
    ps = new char[len];
}
~myString(){
    if(ps)
        delete[] ps;
}

拷贝构造:myString s2 = s1; 等位拷贝:s1的ps指针赋给s2的ps

s1.ps 和 s2.ps同时指向同一个内存 s1,s2分别析构时,会两次释放同一内存

赋值运算符重载:

代码语言:javascript
复制
myString s1('AB')
myString s2('CD')
s2 = s1;

存在内存泄漏和重析构

深拷贝实现
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018.12.10 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 构造函数与析构函数
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档