zhouwc 的博客

初探类

放入我的博客食用效果更佳

类基础

什么是类

类是C++语言中非常重要的一部分,是C++面向对象编程的基础。类的基本思想是数据抽象封装,其中数据抽象是一种依赖与接口实现的分离设计方法。简单来说,类是C语言中struct的升级版,可以完成更多的操作。

定义类

虽说在C++中,类可以表示为struct或者class,但是类的定义原则上应该使用class而并非struct。class的定义与struct类似。前面我们先使用struct来说明一些关于类的知识,后面我们会说明为何要使用class。

类的基本成员

我们可以在类中定义成员变量与成员函数,或者是宏定义等等。类的成员无法直接在类中初始化,比如你这样写就会CE:

struct A{
    int x=0,y;
}

由于无法直接初始化,所以我们不能在类中直接定义常量(后面会讲如何定义)。类的成员函数与类的成员变量类似,我们可以调用它。比如,下面这段代码会输出10:

#include<cstdio>
struct Key{
    int x,y;
    int jia(){
        return x+y;
    }
};
int main(){
    Key a;
    a.x=8,a.y=2;
    printf("%d",a.jia());
    return 0;
}

声明类

与函数类似,后面的代码不可以使用前面未声明的类,我们可以把类的定义与声明分开来,比如这样:

struct Key;
struct Key{
    int x,y;
    int jia(){
        return x+y;
    }
};

初探类的访问限制

private与public

类中有三种访问说明符:private,public,protected。由于第三种访问说明符要牵涉到类继承的问题,所以我们先讨论前面两种。在定义类时,我们可以使用访问说明符+":"来封装成员,访问说明符的有效范围是冒号之后到下一个访问说明符出现之前或者类结尾。举个例子:

struct Key{
    private:
        int x,y;
    public:
        int jia(){
            return x+y;
        }
        void assign(int x1,int y1){
            x=x1,y=y1;
        }
};

顾名思义,private就是私有的,public就是公有的,外界一般情况下无法直接访问类的私有成员,但是可以访问类的公有成员。对于上面那个类,可以举一个例子:

int main(){
        Key c;
        c.assign(1,2);  //正确,访问了c的公有成员
        c.x=1,c.y=2;  //错误,正常情况下无法访问c的私有成员
        return 0;
}

class的struct的一个很大的区别就是struct默认是public的,而class默认是private的。往往我们在定义类时需要一些私有成员来更好地体现类的封装性,而这些私有成员一般都是要放在前面的,所以我们一般选用class。

友元

某些时候,我们需要一些其他函数或者类来访问某个类的private成员,这时候我们可以通过定义友元来完成。友元可以在类的任意位置声明。举个例子:

class Key{
    private:
        int x,y;
        friend int func(int,int);
};
int func(int x,int y){
    Key c;
    c.x=x,c.y=y;
    return c.x+c.y;
}

一些类的特殊成员

this指针

类中会自动帮你生成一个成员,this指针,这个指针指向它自己,你可以用它来做一点事情。举个例子:

class Key{
    int x,y;
public:
    Key& assign(int x1,int y1){
        x=x1,y=y1;
        return *this;
    }
};

静态成员

与函数的静态成员类似,我们可以使用static标识符来声明静态成员,静态成员可以声明在类的任何位置,但是必须在外面定义。静态成员不属于任何该类型的对象,但是所有该类型的对象都可以直接调用该静态成员。在其它位置,我们可以使用类名::静态成员名来定义,访问,或者修改该静态成员。以Trie树的代码为例,可以了解一下静态成员的用法:

struct TrieTree{
        struct Node{
            int son[2],sum,key;
        };
        static Node tree[(int)(2e7)];
        static stack<int> stk;
        static int top;
        int new_node(){
            int ret;
            if(!stk.empty()){
                tree[stk.top()]=(Node){{0,0},0,0};
                ret=stk.top(); stk.pop();
                return ret;
            }
            else {
                tree[++top]=(Node){{0,0},0,0};
                return top;
            }
        }
        int root;
        void initialize(){
            root=new_node();
        }
        void insert(int key){
            int x=root;  ++tree[x].sum;
            for(int p=27;p>=0;--p){
                int t=(key&(1<<p))>0;
                if(tree[x].son[t]==0){
                    int k=new_node();
                    tree[x].son[t]=k;
                }
                x=tree[x].son[t];
                ++tree[x].sum;
            }
            tree[x].key=key;
        }
        void erase(int key){
            int x=root; --tree[x].sum;
            for(int p=27;p>=0;--p){
                int t=(key&1<<p)>0;
                int y=tree[x].son[t];
                --tree[y].sum;
                if(tree[y].sum==0) 
                    stk.push(y),tree[x].son[t]=0;
                x=y;
            }
        }
        int calc(int key){
            int x=root,ret=0;
            for(int p=27;p>=0;--p){
                int t=(key&1<<p)>0;
                if(t==1) ret+=tree[tree[x].son[0]].sum;
                x=tree[x].son[t];
            }
            return ret;
        }
};
TrieTree::Node TrieTree::tree[(int)(2e7)]; //在外面定义
stack<int> TrieTree::stk;
int TrieTree::top=-1; //在外面初始化

我们可以通过静态成员来定义类的常量:

class Key{
    static const int INF;
};
const int Key::INF=2147483647;

类的创建,拷贝与销毁

定义构造函数

我们可以定义类的构造函数,在类被创建时运行。构造函数无返回值,可以有参数,可以被重载。定义方式是类名+参数列表+(初始化列表)+函数体。构造函数必须是public成员,参数列表与函数体和一般函数类似,初始化列表由冒号开始,由逗号隔开,每一组初始化信息由成员(初值)组成,举个例子:

class Key{
    int x,y,sum;
public:
    Key(int x1,int y1):x(x1),y(y1){
        sum=x+y;
    }
};

我们可以这么调用它:

Key a(0,0);

要注意一点,如果参数列表为空,在调用时不可以加空括号(不然编译器以为你在声明函数)(stl中的很多容器都有无参数的构造函数,这就是你开了200万个priority_queue主程序直接return 0都会超时的原因) 我们还可以定义拷贝构造函数,这个函数一般只有一个其自身类型的常量引用,定义了拷贝构造函数可以实现同类型之间的赋值(如果你不写,编译器也会帮你自动生成一个),对于上面那个类,你可以大致这样写一个:

    Key(const Key& q){
        x=q.x,y=q.y,sum=q.sum;
    }

类的强制类型转换

我们可以通过构造函数来实现强制类型转换,举个例子:

#include<cstdio>
class Key{
    int x,y;
public:
    int sum;
    Key(int x1,int y1):x(x1),y(y1){
        sum=x+y;
    }
};
int main(){
    Key a=(Key){1,2};
    a=Key(1,2);   //这句话作用与上一行完全一样
    printf("%d",a.sum);
    return 0;
}

这段代码会输出3。

聚合类

有一类特殊的类C++中会自动帮你定义一些东西。聚合类满足以下几个条件: 1.所有成员都是public的; 2.不存在构造函数; 3.没有继承任何类,没有虚函数成员(类继承方面我也不是很懂); 你可以按类内变量声明的顺序直接来实现类型转换。

struct Key{
    int x,y;
    std::string s;
    int calc(){
        return x+y;
    }
};
int main(){
    Key a;
    a=(Key){1,2,"123"};   //正确,可以直接类型转换
    a=(Key){1,"wa",2};   //错误,第二个声明的不是string类型
    return 0;
}

初探析构函数

与构造函数相反,析构函数是某该类型的对象因为种种原因被销毁时自动执行的,析构函数没有返回值,没有参数,用一个波浪号来标识。举个例子:

#include<iostream>
struct Key{
    int x,y;
    ~Key(){
        std::cout<<x<<' '<<y;
    }
};
int main(){
    Key a=(Key){0,1};
    return 0;  //函数跳出,对象被销毁,执行析构函数
}    

上述代码会输出"0 1"。

版权声明:此篇文章为本博客管理员“傻逼”所写


2018-09-18 20:59:33 in 未分类