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)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容

  • 3. 类设计者工具 3.1 拷贝控制 五种函数拷贝构造函数拷贝赋值运算符移动构造函数移动赋值运算符析构函数拷贝和移...
    王侦阅读 1,785评论 0 1
  • C++基础2:类与对象 1. 认识类与对象 什么是类(class)?类(class)是类型(type),是用户自定...
    jdzhangxin阅读 2,241评论 0 7
  • 一个博客,这个博客记录了他读这本书的笔记,总结得不错。《深度探索C++对象模型》笔记汇总 1. C++对象模型与内...
    Mr希灵阅读 5,566评论 0 13
  • C++文件 例:从文件income. in中读入收入直到文件结束,并将收入和税金输出到文件tax. out。 检查...
    SeanC52111阅读 2,754评论 0 3
  • 看红字又不免热泪盈眶.. The prison door: But on the one side of the ...
    Hippocrene阅读 2,058评论 0 0