
C类1.类的引入2.类的定义3、访问限定符4.类的作用域5.类的实例化6.计算类对象的大小7.this指针8、构造函数9.析构函数10、拷贝构造函数1.类的引入C语言结构体中只能定义变量在C中结构体内不仅可以定义变量也可以定义函数。比如之前在数据结构初阶中用C语言方式实现的栈结构体中只能定义变量现在以C方式实现会发现struct中也可以定义函数。//stack.h#pragmaonce#includeiostream#includestdlib.h#includestring.h#includeassert.husingnamespacestd;typedefintTypeData;structStack{public://void Init();Stack();voidPush(TypeData x);voidPop();TypeDataTop();intEmpty();intSize();//void Destory();~Stack();private:voidCheakCapacity();private:TypeData*_array;int_size;int_capacity;};//Stack.cpp#includeStack.h//void Stack::Init()//{// _array nullptr;// _size 0;// _capacity 0;//}Stack::Stack(){_arraynullptr;_size0;_capacity0;}Stack::~Stack(){assert(_array);free(_array);_arraynullptr;_size0;_capacity0;}voidStack::Push(TypeData x){CheakCapacity();_array[_size]x;_size;}voidStack::Pop(){assert(_array);_size--;}TypeDataStack::Top(){return_array[_size-1];}intStack::Empty(){assert(_array);return_size0;}intStack::Size(){assert(_array);return_size;}voidStack::CheakCapacity(){if(_size_capacity){intnewcapacity_capacity0?2:_capacity*2;TypeData*tmp(TypeData*)realloc(_array,sizeof(TypeData)*newcapacity);if(tmpnullptr){perror(realloc fail);return;}_arraytmp;_capacitynewcapacity;}}//void Stack :: Destory()//{// assert(_array);// free(_array);// _array nullptr;// _size 0;// _capacity 0;//}//Test.cpp#includeStack.hintmain(){Stack st1;//st1.Init();st1.Push(1);st1.Push(2);st1.Push(3);st1.Push(4);while(!st1.Empty()){coutst1.Top() ;st1.Pop();}//st1.Destory();return0;}上述结构体的定义更喜欢用class来定义2.类的定义class className{// 类体由成员函数和成员变量组成};class为定义类的关键字ClassName为类的名字{}中为类的主体注意类定义结束时后面分号不能省略。类体中内容称为类的成员类中的变量称为类的属性或成员变量; 类中的函数称为类的方法或者成员函数。类的两种定义方式1. 声明和定义全部放在类体中需注意成员函数如果在类中定义编译器可能会将其当成内联函数处理。2. 类声明放在.h文件中成员函数定义放在.cpp文件中注意成员函数名前需要加类名::看下面代码初始化不了。原因是成员变量跟成员函数形参一样。做下面改进3、访问限定符C实现封装的方式用类将对象的属性与方法结合在一块让对象更加完善通过访问权限选择性的将其接口提供给外部的用户使用。【访问限定符说明】public修饰的成员在类外可以直接被访问protected和private修饰的成员在类外不能直接被访问(此处protected和private是类似的)访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止如果后面没有访问限定符作用域就到 } 即类结束。class的默认访问权限为privatestruct为public(因为struct要兼容C)注意访问限定符只在编译时有用当数据映射到内存后没有任何访问限定符上的区别问题C中struct和class的区别是什么解答C需要兼容C语言所以C中struct可以当成结构体使用。另外C中struct还可以用来定义类。和class定义类是一样的区别是struct定义的类默认访问权限是publicclass定义的类默认访问权限是private。注意在继承和模板参数列表位置struct和class也有区别后序给大家介绍。面向对象的三大特性封装、继承、多态。4.类的作用域类定义了一个新的作用域类的所有成员都在类的作用域中。在类体外定义成员时需要使用 :: 作用域操作符指明成员属于哪个类域。classPerson{public:voidPrintPersonInfo();private:char_name[20];char_gender[3];int_age;};// 这里需要指定PrintPersonInfo是属于Person这个类域voidPerson::PrintPersonInfo(){cout_name _gender _ageendl;5.类的实例化用类类型创建对象的过程称为类的实例化。一个类可以实例化出多个对象实例化出的对象 占用实际的物理空间存储类成员变量。Person类是没有空间的只有Person类实例化出的对象才有具体的年龄。intmain(){Person._age100;// 编译失败error C2059: 语法错误:“.”return0;}6.计算类对象的大小classA{public:voidPrintA(){cout_aendl;}private:char_a;};存储形式只保存成员变量成员函数存放在公共的代码段结论一个类的大小实际就是该类中”成员变量”之和当然要注意内存对齐注意空类的大小空类比较特殊编译器给了空类一个字节来唯一标识这个类的对象。7.this指针classData{public:voidInit(intyear,intmonth,intday){_yearyear;_monthmonth;_dayday;}voidPrint(){cout_year-_month-_dayendl;}private:int_year;int_month;int_day;};intmain(){Data d1,d2;d1.Init(2022,1,11);d2.Init(2022,1,12);d1.Print();d2.Print();return0;}Date类中有 Init 与 Print 两个成员函数函数体中没有关于不同对象的区分那当d1调用 Init 函数时该函数是如何知道应该设置d1对象而不是设置d2对象呢C中通过引入this指针解决该问题即C编译器给每个“非静态的成员函数“增加了一个隐藏的指针参数让该指针指向当前对象(函数运行时调用该函数的对象)在函数体中所有“成员变量”的操作都是通过该指针去访问。只不过所有的操作对用户是透明的即用户不需要来传递编译器自动完成。this指针特性.this指针的类型类类型* const即成员函数中不能给this指针赋值。只能在“成员函数”的内部使用来指示成员变量this指针本质上是“成员函数”的形参当对象调用成员函数时将对象地址作为实参传递给this形参。所以对象中不存储this指针。this指针是“成员函数”第一个隐含的指针形参一般情况由编译器通过ecx寄存器自动传递不需要用户传递classA{public:voidPrint(){coutthisendl;coutPrint()endl;}private:int_a;};intmain(){//定义了一个A类型的指针pA*pnullptr;//nullptr传给了Print里面的this指针没影响//跟访问结构体一样可以访问p-Print();//A a;//a._a 1;//A* pp a;//cout pp-_a pp-_a endl;return0;}classA{public:voidPrintA(){cout_aendl;}private:int_a;};intmain(){//A a;//A* p a;A*pnullptr;//this是nullptr进入PrintA之后找不到_a;p-PrintA();return0;}8、构造函数构造函数是一个特殊的成员函数名字与类名相同,创建类类型对象时由编译器自动调用以保证每个数据成员都有 一个合适的初始值并且在对象整个生命周期内只调用一次。构造函数特性如下classDate{public://1.无参构造函数Date(){}//2.带参构造函数Date(intyear,intmonth,intday){_yearyear;_monthmonth;_dayday;}private:int_year;int_month;int_day;};intmain(){Date d1;//定义了d1对象并且自动调用了无参构造函数他支持函数重载Dated2(2025,1,1);//定义d2选项调用带参的构造函数// 注意如果通过无参构造函数创建对象时对象后面不用跟括号否则就成了函数声明// 以下代码的函数声明了d3函数该函数无参返回一个日期类型的对象// warning C4930: “Date d3(void)”: 未调用原型函数(是否是有意用变量定义的?)Dated3();return0;}如果类中没有显式定义构造函数则C编译器会自动生成一个无参的默认构造函数一旦用户显式定义编译器将不再生成。classDate{public:/* // 如果用户显式定义了构造函数编译器将不再生成 Date(int year, int month, int day) { _year year; _month month; _day day; } */voidPrint(){cout_year-_month-_dayendl;}private:int_year;int_month;int_day;};intmain(){// 将Date类中构造函数屏蔽后代码可以通过编译因为编译器生成了一个无参的默认构造函数// 将Date类中构造函数放开代码编译失败因为一旦显式定义任何构造函数编译器将不再生成// 无参构造函数放开后报错error C2512: “Date”: 没有合适的默认构造函数可用Date d1;return0;}上面说是系统自动生成构造函数进行初始化成员变量但是变量里面是随机值。关于编译器生成的默认成员函数很多童鞋会有疑惑不实现构造函数的情况下编译器会生成默认的构造函数。但是看起来默认构造函数又没什么用d对象调用了编译器生成的默认构造函数但是d对象_year/_month/_day依旧是随机值。也就说在这里编译器生成的默认构造函数并没有什么用解答C把类型分成内置类型(基本类型)和自定义类型。内置类型就是语言提供的数据类型如int/char…自定义类型就是我们使用class/struct/union等自己定义的类型看看下面的程序就会发现编译器生成默认的构造函数会对自定类型成员_t调用的它的默认成员函数(不传参就调用的那个函数)。classTime{public:Time(){coutTimee()endl;_hour0;_minute0;_second0;}private:int_hour;int_minute;int_second;};classDate{private:int_year;int_month;int_day;//自定义类型Time _t;};intmain(){Date d;return0;}C11 中针对内置类型成员不初始化的缺陷又打了补丁即内置类型成员变量在类中声明时可以给默认值。在类的声明处直接赋值即可。无参的构造函数和全缺省的构造函数都称为默认构造函数并且默认构造函数只能有一个。注意无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数都可以认为是默认构造函数。9.析构函数typedefintDataType;classStack{public://构造函数Stack(size_t capacity3){_array(DataType*)malloc(sizeof(DataType)*capacity);if(nullptr_array){perror(malloc申请空间失败!!!);return;}_capacitycapacity;_size0;}voidCheckCapacity(){if(_size_capacity){_array(DataType*)realloc(_array,sizeof(DataType)*_capacity*2);if(nullptr_array){perror(malloc申请空间失败!!!);return;}_capacity*2;}}voidPush(DataType data){CheckCapacity();_array[_size]data;_size;}DataTypeTop(){return_array[_size-1];}voidPop(){_size--;}intEmpty(){return_size0;}//析构函数~Stack(){if(_array){free(_array);_arrayNULL;_capacity0;_size0;}}private:DataType*_array;int_capacity;int_size;};关于编译器自动生成的析构函数是否会完成一些事情呢下面的程序我们会看到编译器生成的默认析构函数对自定类型成员调用它的析构函数。classTime{public:~Time(){cout~Time()endl;}private:int_hour;int_minute;int_second;};classDate{private:// 基本类型(内置类型)int_year1970;int_month1;int_day1;// 自定义类型Time _t;};intmain(){Date d;return0;}// 程序运行结束后输出~Time()// 在main方法中根本没有直接创建Time类的对象为什么最后会调用Time类的析构函数// 因为main方法中创建了Date对象d而d中包含4个成员变量其中_year, _month, _day三个是内置类型成员销毁时不需要资源清理最后系统直接将其内存回收即可而_t是Time类对象所以在d销毁时要将其内部包含的Time类的_t对象销毁所以要调用Time类的析构函数。但是main函数中不能直接调用Time类的析构函数实际要释放的是Date类对象所以编译器会调用Date类的析构函数而Date没有显式提供则编译器会给Date类生成一个默认的析构函数目的是在其内部调用Time类的析构函数即当Date对象销毁时要保证其内部每个自定义对象都可以正确销毁// main函数中并没有直接调用Time类析构函数而是显式调用编译器为Date类生成的默认析构函数// 注意创建哪个类的对象则调用该类的析构函数销毁那个类的对象则调用该类的析构函数。如果类中没有申请资源时析构函数可以不写直接使用编译器生成的默认析构函数比如Date类有资源申请时一定要写否则会造成资源泄漏比如Stack类。10、拷贝构造函数拷贝构造函数只有单个形参该形参是对本类类型对象的引用(一般常用const修饰)在用已存在的类类型对象创建新对象时由编译器自动调用。拷贝构造函数特征拷贝构造函数是构造函数的一个重载形式。拷贝构造函数的参数只有一个且必须是类类型对象的引用使用传值方式编译器直接报错因为会引发无穷递归调用。传参问题。classDate{public:Date(intyear1,intmonth1,intday1){_yearyear;_monthmonth;_dayday;}Date(Dated){coutDate(Date d)endl;_yeard._year;_monthd._month;_dayd._day;}voidPrint(){cout_year-_month-_dayendl;}private:int_year1;int_month1;int_day1;};voidfun(Date d){d.Print();}intmain(){Dated1(2025,1,4);fun(d1);//类对象传参先调用拷贝构造在执行Print();return0;}看上面函数的调用类对象传值传参//类对象传参先调用拷贝构造在执行Print()若未显式定义编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝这种拷贝叫做浅拷贝或者值拷贝。classTime{public:Time(){_hour1;_minute1;_second1;}Time(constTimet){_hourt._hour;_minutet._minute;_secondt._second;coutTime::Time(const Time)endl;}private:int_hour;int_minute;int_second;};classDate{private:// 基本类型(内置类型)int_year1970;int_month1;int_day1;// 自定义类型Time _t;};intmain(){Date d1;// 用已经存在的d1拷贝构造d2此处会调用Date类的拷贝构造函数// 但Date类并没有显式定义拷贝构造函数则编译器会给Date类生成一个默认的拷贝构造函数Date d2d1;return0;}类中如果没有涉及资源申请时拷贝构造函数是否写都可以一旦涉及到资源申请时则拷贝构造函数是一定要写的否则就是浅拷贝。