C++基础 | 类和对象

class Circle
{
private:
        double radius ; //成员变量 
public : //类的访问控制
         void Set_Radius( double r )
         {
            radius = r;
         }               //成员函数
          double Get_Radius()
          {
              return radius;
          }.     //通过成员函数设置成员变量 
          double Get_Girth()
          {
                return 2 * 3.14f * radius;
           }     //通过成员函数获取成员变量 
           double Get_Area()
           {
                return  3.14f * radius * radius;
            }
};

也可以这样:

class Date
{ 
public:
    void init(Date &d);
    void print(Date & d);
    bool isLeapYear(Date & d);
private:
    int year;
    int month;
    int day; };
    void Date::init(Date &d)
    {
        cout<<"year,month,day:"<<endl;
        cin>>d.year>>d.month>>d.day;
    }
    void Date::print(Date & d)
    {
        cout<<"year month day"<<endl;
        cout<<d.year<<":"<<d.month<<":"<<d.day<<endl;
    }
    bool Date::isLeapYear(Date & d)
    {
        if((d.year%4==0 && d.year%100 != 0) || d.year%400 == 0)
            return true;
        else
            return false;
}

封装

封装有2层含义(把属性和方法进行封装对属性和方法进行访问控制)
Public修饰成员变量和成员函数可以在类的内部和类的外部被访问。
Private修饰成员变量和成员函数只能在类的内部被访问。
Protected保护控制权限,在类的继承中跟private有区别,在单个类中和private是一样的。

构造与析构

class Test {
  public: 
    Test() {     // 无参数构造函数
          ; 
    }
      Test(int a, int b) {    //带参数的构造函数 
      ;
   }

Test(const Test &obj) {  //拷贝构造函数 
    ;
   }
~Test(){.  //析构函数
;
}
    private:
        int a;
        int b;
 };

构造函数和析构函数都不能有返回值,且析构函数不能有参数。
析构函数的作用,并不是删除对象,而在对象销毁前完成的一些清理工作。
即:

构造规则:
1 在对象创建时自动调用,完成初始化相关工作。
2 无返回值,与类名同,默认无参,可以重载,可默认参数。
3 一经实现,默认不复存在。

析构规则:
1 对象销毁时,自动调用。完成销毁的善后工作。
2 无返值 ,与类名同。无参。不可以重载与默认参数
3.析构函数调用顺序,谁先构造的,谁就后析构

若没写构造函数和析构函数,编译器会提供默认的构造函数和析构函数,通常函数里面什么都没有

拷贝构造函数
class Test {
  public:
    Test()  {
        cout<<"我是无参构造函数,被调 了"<<endl;
     }
     Test(int a)  {
        m_a = a; 
      }
  Test(const Test &another_obj) //拷贝构造函数 {
            cout<<"我也是构造函数,我是通过另外一个对象, 来初始化我  "<<endl;
            m_a = another_obj.m_a;
        }
    ~Test() {
  cout<<"我是析构函数, 动被调 了"<<endl; 
    }
        void printT()
        {
            cout << "m_a = " << m_a <<endl;
        }
  void operator=(const Test &another_obj){  //重载操作符
   m_a = another_obj.m_a;
 }

    private:
        int m_a;
};


int main(){
  Test  t1(3);   //构造函数是初始化时调用
  Test  t2(t1);   //调用了拷贝构造函数
  Test  t3=t1;   //调用了拷贝构造函数
  Test  t4;      //没有调用了拷贝构造函数,因为是无参,调用的是无参构造函数
    t4=t1;        //重载操作符,可以达到拷贝构造函数的效果,但无调用拷贝构造函数
}
//这里是t1最后析构
有关拷贝构造的应用场景和匿名对象
#include <iostream>
using namespace std;
class Location
{
//带参数的构造函数
public:
Location( int xx = 0 , int yy = 0 ) {
            X = xx ;
            Y = yy ;
            cout << "Constructor Object." <<endl;
}
Location(const Location & obj) //copy构造函数 
{
            X = obj.X;
            Y = obj.Y;
            cout <<"Copy Constructor." <<endl;
}
        ~Location()
        {
            cout << X << "," << Y << " Object destroyed." << endl ;
        }
        int  GetX () {
            return X;
}
        int GetY () {
            return Y;
} 
private :
    int X;
    int Y; 
};

//结论1 : 函数的返回值是一个元素 (复杂类型的), 返回的是一个新的匿名对象(所以会调匿名 对象类的copy构造函数)
//
//结论2: 有关匿名对象的去和留
//如果 匿名对象初始化给另外一个同类型的对象, 匿名对象转成有名对象 
//如果 匿名对象赋值给另外一个同类型的对象, 匿名对象被析构

Location g()
{
    Location temp(1, 2);
    return temp;
}  //匿名的对象=temp  匿名对象. 拷贝构造(temp),然后temp析构
void test1()
{
g(); 
}
void test2()
{
// 匿名对象初始化m 此时c++编译器 直接把匿名对转成m; (扶正) 从匿名转成有名字了m 
//就是将这个匿名对象起了名字m,他们都是同一个对象
Location m = g(); //匿名对象,被扶正,不会析构掉

cout<<m.GetX()<<endl;;
}
void test3()
{
// 匿名对象 赋值给 m2后, 匿名对象被析构
Location m2(1, 2);
m2 = g();   //因为匿名对象=给m2, 匿名对象,被析构

cout<<m2.GetX()<<endl;;
}
int main(void)
{
    test1();
    test2();
    test3();
return 0; 
}
浅拷贝与深拷贝

默认拷贝构造函数
很多时候在我们都不知道拷贝构造函数的情况下,传递对象给函数参数或者函数返回对象都能很好的进行,这是因为编译器会给我们自动产生一个拷贝构造函数,这就是“默认拷贝构造函数”,这个构造函数很简单,仅仅使用“老对象”的数据成员的值对“新对象”的数据成员一一进行赋值

所谓浅拷贝,指的是在对象复制时,只对对象中的数据成员进行简单的赋值,默认拷贝构造函数执行的也是浅拷贝。大多情况下“浅拷贝”已经能很好地工作了,但是一旦对象存在了动态成员,那么浅拷贝就会出问题了

ps:定义在类里的成员变量 string a:内部会帮你做好深拷贝

#include <iostream>
#include <assert.h>
using namespace std;

class Test {
  public:
    Test()  {
        cout<<"我是无参构造函数,被调了"<<endl; 
        p=new int(10);
        }
     Test(int a)  {
        m_a = a; 
      }
  
    ~Test() {
  cout<<"我是析构函数, 被调了"<<endl; 
  assert(p!=NULL);
            delete p;    //这里会出现异常,因为t2和t1指向同一个堆,一个内存区不能同时delete两次。
  }
        void printT()
        {
            cout << "m_a = " << m_a <<endl;
            
        }

  

    private:
        int m_a;
        int *p;
};


int main(){
  Test  t1;
  Test  t2(t1);   
 
}

使用默认拷贝构造函数都是浅拷贝。 有时候浅拷贝不会发生异常,当涉及动态变量时就可能发生异常。
解决方案: 手工编写拷构造函数 使用深copy
如 在拷贝函数中用new 或 malloc开辟新的内存区来赋值。

这个帖子里,大佬写得很好(https://www.cnblogs.com/alantu2018/p/8459250.html

构造函数的初始化列表
#include <iostream>
using namespace std;
class ABC
{
public:
    ABC(int a, int b, int c)
    {
        this->a = a;
        this->b = b;
        this->c = c;
        printf("a:%d,b:%d,c:%d \n", a, b, c);
        printf("ABC construct ..\n");
    }
~ABC() {
        printf("a:%d,b:%d,c:%d \n", a, b, c);
        printf("~ABC() ..\n");
    }
private:
    int a;
    int b;
    int c; 
    };

class MyD
{
public:
    MyD():abc1(1,2,3),abc2(4,5,6),m(100)   //构造函数的初始化列表,冒号后面是直接初始化,调用构造函数
    {
        cout<<"MyD()"<<endl;
    }    
    ~MyD() {
        cout<<"~MyD()"<<endl;
    }
private:
    ABC abc1;
    ABC abc2;
    const int m;
};
int main()
 {
MyD myD;
return 0;
 }

构造顺序与初始化列表顺序无关,按构造对象成员的顺序

#######构造中调用构造是危险的行为

#include <iostream>
using namespace std;
//构造中调用构造是危险的行为 
class MyTest
{
public:
    MyTest(int a, int b, int c)
    {
        _a = a;
        _b = b;
        _c = c;
}
    MyTest(int a, int b)
    {
        _a = a;
        _b = b;
    MyTest(a, b, 100); //产生新的匿名对象 ,之后会被析构
    }
    ~MyTest() {
        printf("MyTest~:%d, %d, %d\n", _a, _b, _c);
    }
int getC() 
{
    return _c;
     }
void setC(int val)
{
    _c = val; 
}
private:
    int _a;
    int _b;
    int _c;
     };
int main()
 {
    MyTest t1(1, 2);
    printf("c:%d\n", t1.getC()); //请问c的值是?  答案是乱码
    return 0;
}

new和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 类型(常量);
指针变量=new类型[表达式];
作用:从堆分配一块"类型"大小的存储空间,返回首地址
其中:“常量”是初始化值,可缺省
创建数组对象时,不能为对象指定初始值

delete运算符释放已分配的内存空间
使用形式: delete 指针变量;
delete[] 指针变量;
其中:“指针变量”必须是一个new返回的指针

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

malloc不会调用类的构造函数,而new会调用类的构造函数 !!!!!
Free不会调用类的析构函数,而delete会调用类的析构函数!!!!

静态成员变量和成员函数

静态成员变量

//声明
static 数据类型 成员变量; //在类的内部

//初始化
数据类型 类名::静态数据成员 = 初值; //在类的外部

//调用
类名::静态数据成员
类对象.静态数据成员

1,static 成员变量实现了同类对象间信息共享。
2,static 成员类外存储,求类大小,并不包含在内。
3,static 成员是命名空间属于类的全局变量,存储在 data 区。
4,static 成员只能类外初始化。 初始化后想改的话要通过静态成员函数
5,可以通过类名访问(无对象生成时亦可),也可以通过对象访问。

#include <iostream>
using namespace std;
class Box
{
public:
    Box(int l, int w):length(l),width(w) {
    }
    int volume()
    {
        return length * width * height;
    }
    static int height;
    int length;
    int width;
};

int Box::height = 5;

int main() {
    cout<<Box::height<<endl;
    Box b(1,1);
    cout<<b.height<<endl;
    cout<<b.volume()<<endl;
return 0; 
}
静态成员函数

//声明
static 函数声明
//调用
类名::函数调
类对象.函数调

1,静态成员函数的意义,不在于信息共享,数据沟通,而在于管理静态数据成员, 完 成对静态数据成员的封装。
2,静态成员函数只能访问静态数据成员。原因:非静态成员函数,在调用时this 指 针被当作参数传进。而静态成员函数属于类,而不属于对象,没有 this 指针。

#include <iostream>
using namespace std;
class Student
{
public:
    Student(int n,int a,float s):num(n),age(a),score(s){}
    void total()
    {
count++;
        sum += score;
    }
    static float average();
private:
    int num;
    int age;
    float score;
    static float sum;
    static int count;
};
float Student::sum = 0;
int Student::count = 0;
float Student::average() {    //函数可以在类里面定义,但是不能访问类的对象內的成员,因为this只能用于非静态成员函数内部
    return sum/count;
}
int main() {
    Student stu[3]= {
        Student(1001,14,70),
        Student(1002,15,34),
        Student(1003,16,90)
};
    for(int i=0; i<3; i++) {
        stu[i].total();
}
cout<<Student::average()<<endl;
return 0; 
}
static占用的大小

只有类中的普通成员变量算进类的大小,静态成员变量和函数不算进去。

class C1 {
    public:
        int i;  //4
int j; //4
        int k;  //4
}; //12
class C2 {
    public:
        int i;
        int j;
         int k;
        static int m;                    //4,但不算进去
    public:
        int getK() const { return k; }     //4,但不算进去
        void setK(int val) { k = val; }    //4,但不算进去
};//12

C++类对象中的成员变量和成员函数是分开存储的
成员变量: 普通成员变量:存储于对象中,与struct变量有相同的内存布局和字节对
齐方式
静态成员变量:存储于全局数据区中
成员函数:存储于代码段中。

以下很重要
1、C++类对象中的成员变量和成员函数是分开存储的。C语言中的内存四区模 型仍然有效!
2、C++中类的普通成员函数都隐式包含一个指向当前对象的this指针。
3、静态成员函数、成员变量属于类
4、静态成员函数与普通成员函数的区别 静态成员函数不包含指向具体对象的指针 普通成员函数包含一个指向具体对象的指针

This
#include <iostream>
using namespace std;
class Test {
    public:
        Test(int a, int b) //---> Test(Test *this, int a, int b)
        {
        this->a = a;     //this是一个常指针Test* const this,只指向对象本身,本能再指向其它
        this-> b = b; }
        void printT()
        {
            cout<<"a: " <<a <<endl;
            cout<< "b: " << this->b <<endl;
        }
    protected:
    private:
            int a;
            int b; 
};
int main(void)
{
    Test t1(1, 2);  //===> Test(&t1, 1, 2);
    t1.printT();    // ===> printT(&t1)
    return 0;
 }

(1)若类成员函数的形参和类的属性,名字相同,通过this指针来解决。
(2)类的成员函数可通过const修饰

全局函数和成员函数
Test& add(Test &t2)             //*this //函数返回引用 
 {
this->a = this->a + t2.getA();
this->b = this->b + t2.getB();    
return *this;             //*操作让this指针回到元素状态  //如果想返回一个对象的本身,在成员方法中,用*this返回
}
Test add2(Test &t2)       //*this //函数返回元素 
{
  //t3是局部变量
Test t3(this->a+t2.getA(), this->b + t2.getB()) ;
 return t3;
}

如果想对一个对象连续调用成员方法,每次都会改变对象本身,成员方法需要返回引用。
如:T1.add(t2).add(t2)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值