C++ ——类和对象

14 篇文章 1 订阅

类和对象

类: 具有相同 属性 (变量)和 行为 (函数)的对象的集合。
类规范由两个部分组成:
+ 类声明:以数据成员的方式描述数据部分。(蓝图)
+ 类方法定义:描述如何实现类成员函数。(细节)


class  CPeople 
{ 
public :   //不加public的话默认为private 
    int  a ; 
    void  fan ( ) 
    { 
        cout << "han" << endl ; 
    } 
} ;   //内定义的花括号后面也会有一个;像结构体一样。在C++中结构体是一个特殊的类。 
int  main ( ) 
{
    //声明一个类的对象
    CPeople op;  //声明一个栈区变量
    op.a=12;
    op.fan();
    //声明指针对象是需要new申请空间
    CPeople* ep = new CPeople;  //申请一个堆区变量
    
    delete ep;
    return 0;
}

  • 内定义的花括号后面也会有一个;像结构体一样。在C++中结构体是一个特殊的类。

访问修饰符

  • public:共有的,都可以使用
  • private:纯私有,类内可见
  • protected:受保护的,类内、子类可见

struct和class创建类的区别

  • class创建类,不添加访问修饰符,默认为私有成员
  • struct创建类,不添加访问修饰符,默认为公有成员

构造函数

  • 只有静态、常量、整形(static const int)的数据成员才能在类中初始化。但是只有初始化后的数据成员才能被使用。
  • 构造函数的作用就是为数据成员初始化。
class Cstu
{
    int age;
    float f;

    Cstu()  //构造函数,名字与类名相同
    {
        age=12;
        f=12.12;
    }
};

构造函数是在main()函数中创建类对象的时候自动调用。

构造函数的参数传递

int main()
{
    CStu stu(12);  //构造函数的参数传递
    CStu* stu1 = new CStu(13,12m5f);  //栈区 指针的定义

    cout << stu.age << endl; 
}

多个构造函数为函数的重载,根据调用参数来选择调用那个函数

当我们不写构造函数时,系统会自动调用一个参数和函数体都为空的构造函数,当我们一旦自己创建了一个构造函数时,系统默认的构造函数将会被删除。系统默认的构造函数与我们自己创建的构造函数为覆盖的关系,而不是重载的关系。

构造函数可以在类内声明类外定义

class Cstu
{
    int age;
    float f;

    Cstu(int age=12,float f=13,6)  //构造函数,名字与类名相同
    {
        age=12;
        f=12.12;
    }
};
//类内声明内外定义的时候,在类外定义的构造函数前面要加上类名作用域Cstu::这样才知道该构造函数是哪个类的。
Cstu::Cstu(int age=12,float f=13,6)
{
    age=a;
    f=b;
};

析构函数

对象在释放的时候自动创建的一个函数。

class CStu
{
    int* pp;
public:
    int age;
    CStu()  //构造函数
    {
        cout << "构造函数" <<endl;
        pp =new int(10);
    }
    ~CStu()  //析构函数
    {
        cout << "析构函数" <<endl;
        delete pp;
    }
};

class person
{
  // 构造函数
  person()
  {
    cout<<"无参构造函数"<<endl;
  }
  
  person(int a)
  {
    cout<<"有参构造函数"<<endl;
  }
  
  // 析构函数
  ~person()
  {
    cout<<"析构函数"<<endl;
  }
};

void text()
{
  person p;
  // 不能写成person p(),这样会被当成一个函数的声明
}

int main(void)
{
  text();
  // 这个函数调用后会自动调用 p的构造函数和析构函数,
  // 在该函数开始调用的时候调用构该函数
  // 在该函数调用完退出时调用析构函数
  // 会输出: 构造函数
  //         析构函数
  
  person b;
  // 这个函数调用后会自动调用构造函数
  // 会输出: 构造函数
  // 在按任意键推出结束程序的时候才会有析构函数调用,但是那个时候控制台关闭了
 
 system("pasue");
}

  • 注意:调用默认构造函数的时候需要不要加()不然会认为是定义了一个函数

拷贝构造

形式:给构造函数传递一个类的引用

CStu(const CStu& a)
{

}

什么时候会用到靠背构造

  1. 当新定义一个对象,并把另一个对象作为新对象的参数的时候
CStu s1;
Cstu s1new(s1);  // 这个时候会直接调用调用拷贝构造
  1. 现有对象给新的对象赋值
CStu s1;
Cstu s1new = s1 ;  // 这个时候会直接调用调用拷贝构造
  1. 通过一个临时对象给新的对象初始化
CStu s1;
Cstu s1new = Cstu(s1) ;  // 这个时候会直接调用调用拷贝构造

拷贝构造的功能

默认的拷贝构造函数, 逐个的复制非静态成员的值,复制的是成员的值。

  // 拷贝构造
 person(const person &p) //参数要是同名的
  
}

int main()
{
  // 拷贝构造函数的调用
  person p1;
  person p2(p1);  // 调用拷贝构造,参数是一个对象
                  // p2的值都拷贝自p1
  
}

括号发、显式法、隐式发

//括号法
person p;
person p0(10);
person p1(p);

// 显式法

person p2=person(10); // 显示法有参构造

person p3=person(p2); // 显示法拷贝构造 

// 隐式法
person p4=10;  // 就相当于 person p4 = person(10);

person p5 = p1; // 隐式拷贝构造

构造函数的三种使用时机

//
void DoWork1(person p)
{
  person p1(10);
  person p2(p1);
}


void 
//二、 值传递的方式给函数传参
void DoWork2(person p)
{
  
}
// 当一个函数的参数为一个类对象的时候,调用该函数也会运行该类的构造函数和析构函数


//三、 值参数返回类对象
person DoWork3()
{
  person p1;
  return p1;
}
// 调用时也会调用类的 构造函数和析构函数

  • 如果我们写了有参构造函数,那么编辑器就不会创建默认构造,但是依然会创建默认拷贝构造
  • 如果我们写了拷贝构造函数,那么编辑器就不会创建其他的构造函数(默认构造函数)

浅拷贝和深拷贝

  • 浅拷贝:简单的拷贝赋值操作
  • 深拷贝:在堆区从新申请空间进行拷贝构造
浅拷贝
  • 浅拷贝会有一个问题:会出现重复释放内存空间而出错的问题
class person
{
public:
  int m_age;
  int m_height;
  
person(int age,int height)
{
  m_age = age;
  m_height = new int(height);
  cout<<"构造函数"<<endl;
}
~person()
{
  // 将堆区代码释放
  if(m_height!=NULL)
  {
    delete m_height;
    m_height=NULL}
}
void doWork()
{
  person p1(20,180);
  
  // 拷贝构造
  person p2(p1);
}
int main()
{
  doWork();
  // 在调用的时候会出错。
}
}
  • 主函数调用doWork()函数时会出错原因
    1. 在调用的时候会出错。
    2. p1申请的height空间,拷贝构造也会拷贝同样的,空间地址也一样
    3. 堆区释放先进后出,在程序结束时,先调用拷贝构造p2的析后函数把释放了他自己height空间,然后p1在调用析构函数的时候,就会出现重复释放内存空间的情况。
深拷贝构造
  • 解决浅拷贝构造的方法是深拷贝构造,自己创建一个一个拷贝函数,而不是使用系统为你创建的拷贝构造
person(const person &p)
{
  m_Age = p.m_age;
  m_height = new int(*p.m_height); 
}
  • 总结:如果有在堆区开辟内存空间的的操作,一定要自己提供拷贝构造函数(深拷贝),这样可以防止拷贝到来的问 题

类对象作为类成员

  • 当一个a类中定义了一个其他b类的对象时,a类会先构造b类的构造函数,在构造b类的构造函数(构造函数先他人,后自身)。
  • 析构函数与构造函数相反(先自身,后他人 )

静态成员函数

  • 静态成员函数只能访问静态成员变量(static只能访问static),不能访问非静态成员变量
class person
{
 public:
 
 static int a;
 int b; 
   static void fun()
 {
   a=100; // 可以访问 
   b=200; // 不可以
 } 
};

this

  • 成员变量成员函数时分开储存的。
  • 非静态成员变量所占内存空间在对象类对象空间中
  • 非静态成员函数静态成员变量所占空间不在类的对象的空间中

用途:

  • 形参和实参同名的时候,用this指针来区分,
  • 在类的非静态成员函数中返回对象本身用return *this

this是一个指针,是当前类的一个指针(类型为例如Cas*[),区分类中的变量还是构造函数中的局变量

class CStu
{
public:
    int a;

    CStu(int a)
    {
        this->a = a; // this->a这个a就表示第四行中的int a的a,是CStu类本身的a
    }
};  

this指针不是成员,是类成员函数的隐含参数

空指针访问成员函数

  • 空指针不能访问成员,否则会出错,为避免报错,可以添加一个空指针判断
    因为到函数在调用成员属性的时候,每个成员属性在他的前面都默认加了一个this(this->age),告诉你这是当前对象的一个属性,但是p是一个空指针。
clacc person
{
publicint age
    void show_1()
    {
        cout << "this is person class" << endl;
    }
    void show_2()
    {
        cout << "age=" << age << endl;
    }
}

void text()
{
    person *p = NULL; // 一个person类的空指针
    p->show_1(); //调用函数1的时候会正常调用
    p->show_2(); //会出现错误
    // 因为到函数2在调用成员属性的时候,每个成员属性在他的前面都默认加了一个this(this->age),告诉你这是当前对象的一个属性,但是p是一个空指针。
    // 函数1没有指针的问题,只是一个输出函数。
}
// 避免出现错误
 void show_2()
    {
        //添加一个空指针判断
        if(this == NULL)
        {
            return;
        }
        cout << "age=" << age << endl;
    }

初始化列表

初始化列表:给数据成员进行初始化。
构造函数:给数据成员进行赋值。

使用初始化列表的四种情况

  1. 初始化成员是对象
  2. 常量(const)成员变量
  3. 子类初始化父类成员
  4. 类成员为引用(&)类型的时候
  • 初始化和赋值的区别
int a = 12 ; // 初始化

int  b ;
b = 4 ;  // 赋值

初始化列表的写法:在构造函数的()后面+:变量(值)

CStu():a(12),f(12.3f)  //这样给初始化为12,f初始化为12.3
{

};

初始化列表中初始化的顺序,由变量被定义的顺序决定,不由初始化列表中的顺序决定。

CStu(int c) : a(c)
{

}

c是一个局部变量,用c给赋值,它的作用范围就是这个构造函数,出了这个函数,它的值就会被回收,当出了这个范围之后,再次调用a的时候值就不确定了

常对象 const

  • 常对象必须在定义对象的时候指定是常对象
    A const a;
    // a是一个常对象
    

常对象中的数据成员都是常变量,且都要有初始化值

  • 常对象只能调用该对象的const类型的函数

常函数

形式:在函数名后面的()后面+const

class CStu
{
public:
    void show() const //常函数
    {
    }
}; 

常函数中不能修改类中的数据成员,但是可以使用,在常函数中自己定义的变量是可以修改的

  • 常对象不可以访问非常成员函数
  • 非常对象可以访问常成员函数
  • 常成员函数只能访问常规对象中的数据成员不能修改常对象中的数据成员
    常对象(const CStu st;)不能调用常函数,但是可以调用普通函数
class CStu
{
public: 
    int fun()
    {}
    void show() const //常函数
    {}
}; 

int main()
{
    const CStu st;  //常对象
    st.show()  //不可以,常对象不能调用常函数
    st.fun()   //可以,常对象能调用普通函数
}

常指针

  • 常指针
int*  const  p ; 
  • 巧记:const离得近,钉住了指针
    指着所指向的指向的位置不能变化,赋值后不能改变指向别处;但是指向对象,该对象的值可以改变

  • 指向常量的指针

		const int* p; 
  • 巧计:const离得远,钉不住指针
    指针所指向的对象,该对象是一个const类型的量, 该对象的值不能改变,但是指针的指向可以改变

静态类

静态成员 static

class CStu
{
public:
    static int a;  // 静态成员类外初始化
    CStu()
    {
    
    }
}; 
int CStu::A=12; // 静态成员类外初始化

只有静态 (static) 常量( const) 整形(int,char,long,short)成员才能在类中初始化

  • 静态成员函数不能调用类中的数据成员和成员函数,可以调用静态成员。(静态成员函数只能使用静态成员变量。)
class CStu
{
public:
    int a; 
   static void fun()
   {
       cout << "static menber";
       a=12;  //不行,静态成员函数不能调用类中的数据成员和成员函数
   }
}; 
  • 静态成员函数没有this指针。
    this指针,是在main()函数中创建类成员的时候才会有this指针。而静态成员可直接有类名作用域(CStu::a=12)调用。这就说明:静态成员和对象并没有直接的关系,它是我们类本身的一个属性,只要类存在,静态成员就会存在,他在内存中就是有空间的。

友元

在一个类中定义一个友元函数,在友元函数就可以调用该类的私有成员

  • 定义方法是在函数前面+friend:friend void fun()

class Cstd
{
private:
    int age;
    void fun()
    {
        age=12;
        cout<<age<<endl;
    }

    friend void chazhao();  //把chazhao()函数声明生Cstd的一个友元,他就可以调用他的私有成员。
};
void chazhao()
{
    Cstd stu;
    stu.fun();  // chazhao函数就可以调用这些私有成员了 
}

访问修饰符为类提供了封闭性,但是友元打破了这种封闭性。

友元类


class Building
{
   // 创建了一个友元类,GoodGay这个类是本类的友元,可以访问私有成员
    friend class GoodGay;
public:
    string SittingRoom 
private:
    string BadRoom ;
public:
Building()
{
    SittingRoom = "客厅";
    BadRoom = "卧室";
}
};

class GoodGay
{
    GoodGay()
    {
        g_Building = new Builidng;
    }

    void visit()
    {
        cout << "好基友正在访问:" << g_Building->SittingRoom << endl;
        // SittingRoom是public类,可以访问
        cout << "好基友正在访问:" << g_Building->BadRoom << endl;
        // BadRoom是private类,由于GoodGay是友元类,所以可以访问

    }

};

成员函数做友元


class Building
{
    // 告诉编译器,GoodGay类下的visit()函数是本类的友元,可以访问私有成员
    friend void GoodGay::visit_1();
public:
    string SittingRoom 
private:
    string BadRoom ;
public:
Building()
{
    SittingRoom = "客厅";
    BadRoom = "卧室";
}
};

class GoodGay
{
    void visit_1()
    {

    }
    void visit_2()
    {

    }

};

内联函数

内联函数类似于C语言中的宏定义,在函数调用的时候,直接把函数体替换内联函数的定义来执行函数

  • 关键词是:inline
inline void fun()  //一个内联函数
{
  
}
inline double square(double x)  //一个内联函数
{
  return x*x;
}
int main()
{
    double a;
    a=square(5.0); 
}

内联函数相较于常规函数,占用的内存稍大,效率高

运算符重载

应用中对象进行相加时,由于不知道对象中的哪个成员需要进行相加,然后对加号进行重新的定义,使得对象可以进行运算。加法乘法等等运算符都可以进行重做。

  • 运算符重载有两种:全局函数重载、成员函数重载
    • 全局函数重载:定义在类外
    • 成员函数重载:定义在类内

全局函数重载

  • 关键词:operator
void operator + (DStu& st,int a)  //+:对+进行重载,传递参数时最好传递对象的引用。
{

}
class CStu
{
public:
    int nage;
    double sScare;
};

// 全局函数重载
void operator+(CStu& st,int a)  
{
    cout <<(st.nage + a) << endl; //表示该加法重载,使类的nage成员+一个int类型常数
}
void operator+(int a , CStu& st)  
{
    cout <<(st.nage + a) << endl; 
}
// 调用的时候根据参数的顺序,调用不同的函数。

int main()
{
    CStu st1,12;

    st1 + a2; //根据参数的类型和顺序会调用第1个
    3 + st2;  //会调用第2个
}

成员函数重载

class Person
{
    int a;
    int b;
    
    // 成员函数重载
    Person operator + (Person &p)
    {
        Person temp;

        temp.a = this->a + p.a ;
        temp.b = this->b + p.b ;

        return temp
    }
};

void test()
{
    Person p1;
    p1.a=10;
    p1.b=11;

    Person p2;
    p1.a=12;
    p1.b=13;

    Person p3;
    p3 = p1 + p2;
}

注意

  • 对于内置的数据类型(int double等)的表达式的运算符是不可以改变的
  • 不要滥用运算符重载

其它运算符重载

<< 运算符重载
  • p是一个类,cout << p 就可以直接输出类中的所有成员变量
  • 只能用全局函数重载<<运算符
// cout的数据类型是ostream(输出流)
ostream &operator << (ostream &cout , person &p)
{
    cout << "a =" << p.a << "b=" << p.b << endl;
    return cout;
}
  • 如果成员变量为私有的成员,可以把重载运算符设置为友元
class person
{
    friend ostream &operator << (ostream &cout , person &p);
private:
    int a;
    int b;
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值