C++学习之类及构造方法

(一)类

  1. 类的一般语法形式

     struct/class 类名:继承方式 基类,...{
     访问控制限定符:
     		类名(形参表):初始化列表{...}//构造函数
     		~类名(void){...}//析构函数
     		返回类型 函数名(形参表){...}//成员函数
     		数据类型 变量名;//成员变量
     };
    
  2. 访问控制限定符
    1)public:公有成员,任何位置都可以使用
    2)private:私有成员,只有类自己的成员函数才能使用
    3)protected:保护成员(后面讲)

  3. 构造函数(constructor)
    1)语法

    class 类名{
    	类名(形参表){
    		//主要负责初始化对象(初始化成员变量)
    	}
    };
    

    2)函数名和类名相同,没有返回类型
    3)构造函数在创建对象时自动被调用,不能像普通的成员函数一样显式的调用。
    注:在每个对象的生命周期,构造函数一定会被调用,但只会被调用一次

  4. 对象的创建和销毁
    1)在栈区创建单个对象
    类名 对象(构造实参表);//直接初始化
    类名 对象=类名(构造实参表);//拷贝初始化,一般和上面等价
    浅析:如果关闭编译器对构造方法的优化,g++ 编译时加上选项-fno-elide-constructors, A a = A(1);首相调用单参构造方法A(int i)创建一个新的临时对象比如它叫tmp,然后再用这个对象作为拷贝构造的参数,拷贝构造:A (const A& a);实际调用时:A(tmp);,然后将拷贝构造所返回的对象,作为最终创建出来的对象。即,在未优化的过程中,执行一次 A a = A(1)实际上调用了两个构造方法,创建了两个对象,不过临时对象用完后会被销毁。该结论已经过测试

    #include <iostream>
    #include <cstring>
    
    using namespace std;
    
    class String{
    public:
    	String(const char *str = NULL){
    		cout << "调用 String(const char *)" << endl;
    		cout << "创建了临时对象" << (void *)this << endl;
    		m_str = new char[strlen(str ? str : "")+1];
    		strcpy(m_str, str);
    	}
    
    	String(const String& that){
    		cout << "调用 copy constructor" << endl;
    		m_str = new char[strlen(that.m_str) + 1];
    		strcpy(m_str, that.m_str);
    		cout << "我要创建的对象已经创建完成,临时对象没什么用了" << endl;
    	}
    
    	~String(void){
    		cout << "析构函数析构了" << (void *)this << endl;
    		delete[] m_str;
    	}
    
    private:
       	char *m_str;
    };
    
    int main(void){
    	String s = "hello";
    	cout << "我正在使用" << (void *)&s << ",它不会被析构" << endl;
    	cout << "程序即将结束了," << (void *)&s << "不用了,即将被析构" << endl;
    	return 0;
    }
    

     
    执行如下:
    在这里插入图片描述

    2)在栈区创建多个对象(对象数组)
    类名 对象数组[元素个数] = {类名(构造实参表),…};

    3)在堆区创建/销毁单个对象//重点掌握
    创建:
    类名* 对象指针 = new 类名(构造实参表);
    注:new操作符在分配完内存后,将会自动调用构造函数完成对象的创建;而如果malloc只能分配内存,不会调用构造函数,不能完成对象的创建.
    销毁:
    delete 对象指针;

    4)在堆区创建/销毁多个对象(对象数组)
    创建:
    类名* 对象指针=new 类名[元素个数]{类名(构造实参表),…}
    销毁:
    delete[] 对象指针;

  5. 多文件编程//参考04project
    1)类的声明放在".h"头文件中
    2)类的实现放在".cpp"源文件中

(二) 构造函数和初始化列表

  1. 构造函数可以重载、也可以带有缺省参数,如string类的构造方法
    (1)string (void);
    (2)string (const string& str);
    (3)string (const string& str, size_t pos, size_t len = npos);
    (4)string (const char* s);
    (5)string (const char* s, size_t n);
    (6)string (size_t n, char c);

    #include <iostream>
    using namespace std;
    class Student{
    public:
        Student(const string& name,int age,int no){
            cout << "构造函数" << endl;
            m_name = name;
            m_age = age;
            m_no = no;
        }
        void who(void){
            cout << "我叫" << m_name << ",今年" <<
                m_age << "岁,学号是" << m_no << endl;
        }
    private:
        string m_name;
        int m_age;
        int m_no;
    };
    int main(void){
        //在栈区创建单个对象
        //Student s("张飞",28,10011);
        Student s = Student("张飞",28,10011);
        s.who();
        //在栈区创建多个对象
        Student sarr[3] = {
                Student("貂蝉",24,10012),
                Student("小乔",23,10013),
                Student("孙尚香",26,10014)};
        sarr[0].who();
        sarr[1].who();
        sarr[2].who();
    
        //在堆区创建单个对象
        Student* ps = new Student("林黛玉",20,10015);
        ps->who();//(*ps).who()
        delete ps;
        ps = NULL;
    
        //在堆区创建多个对象
        Student* parr = new Student[3]{
            Student("潘金莲",28,10016),
            Student("孙二娘",27,10017),
            Student("白骨精",30,10018)};
        parr[0].who();//(parr+0)->who()
        parr[1].who();//(parr+1)->who()
        parr[2].who();//(parr+2)->who()
        
        delete[] parr;
        parr = NULL;
    
    
        return 0;
    }
    
  2. 缺省构造函数(无参构造函数)
    1)如果类中自己没有定义构造函数,那么编译器会为该类提供一个缺省的无参构造函数:
    –》对于基本类型的成员变量,不做初始化
    –》对于类 类型的成员变量(成员子对象),会自动调用相应类的无参构造函数来初始化
    2)如果类中自己定义了构造,无论是否有参数,编译器都不会再提供缺省无参构造函数.

    #include <iostream>
    using namespace std;
    class A{
    public:
        A(void){
            cout << "A的无参构造函数" << endl;
            m_i = 0;
        }
        int m_i;
    };
    class B{
    public:
    	B(void){
    		m_j = 100;
    	}
        int m_j;//基本类型的成员变量
        A m_a;//类类型的成员变量(成员子对象)
    };
    int main(void){
        B b;//匹配B类的缺省无参构造函数
        cout << b.m_j << endl;//100
        cout << b.m_a.m_i << endl;//0
        return 0;
    }
    

     
    在这个B b的过程中,首先分配内存,然后构造B类中的成员子对象(即自定义的类的对象),构造顺序按照声明顺序,依次调用缺省的构造函数(如果不想使用缺省的构造函数,需要使用初始化列表),然后执行B类的构造方法,来初始化B类对象b的其他成员(除了成员子对象以外的成员)

  3. 类型转换构造函数(单参构造函数)
    class 类名{
    [explicit] 类名(源类型){}
    };
    可以实现源类型变量到当前类类型对象的隐式转换功能,如:类名 对象 = 源类型, string s = “123”;在这个过程实例化对象s的过程中,首先调用上面的重载构造方法(4), 即"123"为const char *,生成一个string对象,然后再将这个生成的string对象完全拷贝给s,显示地写为:string s = string(“123”);也可以直接初始化要创建的对象string s(“123”);
    注:可以适用explicit关键字修饰该构造函数,强制要求类型转换的过程必须显式完成.

    #include <iostream>
    using namespace std;
    class Integer{
    public:
        Integer(void){
            cout << "Integer的无参构造" << endl;
            m_i = 0;
        }
        //int->Integer:类型转换构造函数
        /*explicit*/ Integer(int i){
            cout << "Integer的有参构造" << endl;
            m_i = i;
        }
        void print(void){
            cout << m_i << endl;
        }
    private:
        int m_i;
    };
    int main(void){
        Integer i;
        i.print();//0
        //1)先将100作为构造实参,构造一个Integer临
        //时对象;
        //2)再使用临时对象对i进行赋值
        i = 100;//隐式转换
        i.print();//100
        
        //上面隐式转换代码可读性差,推荐使用显式
        //i = (Integer)200;//C风格强制转换
        i = Integer(200);//C++风格强制转换
        i.print();//200
    
    
        return 0;
    }
    
  4. 拷贝(复制)构造函数
    1)用一个已存在的对象作为同类型对象的构造实参,创建新的副本对象,会调用该类的拷贝构造函数。

    class 类名{
    	类名(const 类名& ){
    		...
    	}
    };
    
    A a1(...);
    A a2(a1);//匹配A类的拷贝构造函数
    A a2 = a1;//和上面完全等价
    

    2)如果一个类没有显式定义拷贝构造函数,那么编译器会为该类提供一个缺省的拷贝构造函数:
    –》对于基本类型的成员变量,按字节复制
    –》对于类类型的成员变量(成员子对象),自动调用相应类拷贝构造函数,完成拷贝初始化.

    注:在实际开发中,一般不需要自己定义拷贝构造函数,因为编译器缺省提供的已经很好用了。缺省拷贝为浅拷贝,当成员中有指针时,需要使用自定义深拷贝。

    #include <iostream>
    using namespace std;
    class A{
    public:
        A(int data = 0){
            cout << "A(int=0)" << endl;
            m_data = data;
        }
        //拷贝构造函数
        A(const A& that){
            cout << "A(const A&)" << endl;
            m_data = that.m_data;
        }
        int m_data;
    };
    int main(void){
        const A a1(100);
        cout << a1.m_data << endl;//100
        A a2(a1);//拷贝构造
        //A a2 = a1;//和上面等价
        cout << a2.m_data << endl;//100
    
        return 0;
    }
    

    3)拷贝构造函数调用时机
    –》用已定义的对象作为同类型对象的构造实参
    –》以对象形式向函数传递参数
    –》从函数中返回对象(有可能被编译器优化掉)

    #include <iostream>
    using namespace std;
    class A{
    public:
        A(void){
            cout << "A(void)" << endl;
        }
        A(const A& that){
            cout << "A(const A&)" << endl;
        }
    };
    void func1(A a){}
    A func2(void){
        A a;//无参
        cout << "&a:" << &a << endl;
        return a;//拷贝
    }
    int main(void){
        A a1;//无参
        A a2 = a1;//拷贝
        func1(a1);//拷贝
    
        /* 正常情况func2返回a拷贝到临时对象,临时对象
         * 再拷贝给a3,发生两次拷贝;但是因为编译器优
         * 化让a3直接引用a,不再发生拷贝*/
        //去优化编译选项:
        //g++ 09cpCons.cpp -fno-elide-constructors
        A a3 = func2();//拷贝
        cout << "&a3:" << &a3 << endl;
        return 0;
    }
    
  5. 初始化列表
    1)语法

    class 类名{
    	类名(形参表):成员变量1(初值),成员变量2(初值),... {
    		...
    		...
    	}
    };
    class Student{
    	//先定义成员变量,再赋初值
    public:
    	Student(const string& name,int age,int no){
    		m_name = name;
    		m_age = age;
    		m_no = no;
    	}
    	//定义成员变量同时初始化
    	Student(const string& name,int age,int no)
    		:m_name(name),m_age(age),m_no(no){}
    	
    	string name;
    	int age;
    	int no;
    };
    

    注 !!!!C++初始化类成员时,是按照声明的顺序初始化的,而不是按照出现在初始化列表中的顺序,所以先声明的成员的初始化赋值一定不要依赖后边声明的成员,要把被依赖的放到前边。或者想办法消除依赖关系

    2)大多数情况下,使用初始化列表和原来在构造函数中赋初值结果相同,两者可以任选,但是有些特殊场景,必须要使用初始化列表:
    –》如果有类类型的成员变量(成员子对象),并希望以有参方式对其初始化,则必须使用初始化列表指明需要的构造实参.
    –》子类中构造方法,希望以有参的方式对基类进行初始化
    –》如果有引用型或const修饰的成员变量,必须使用初始化列表来显式的初始化。

    #include <iostream>
    using namespace std;
    int num = 200;
    class A{
    public:
        /*A(void){
            ci = 100;
            ri = num;
        }*/
        A(void):ci(100),ri(num){}
        const int ci;
        int& ri;
    };
    int main(void){
        A a;
        cout << a.ci << "," << a.ri << endl;
        return 0;
    }
    
    #include <iostream>
    using namespace std;
    class A{
    public:
        A(int data){
            cout << "A(int)" << endl;
            m_data = data;
        }
        int m_data;
    };
    class B{
    public:
        //m_a(123):显式指明成员子对象需要的构造实参
        B(void):m_a(123){
            cout << "B(void)" << endl;
        }
        A m_a;//成员子对象
    };
    int main(void){
        B b;
        cout << b.m_a.m_data << endl;//123
        return 0;
    }
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值