类和对象(上 中 下)
类 图纸 / 模板对象 根据图纸造出来的东西1.类的定义类就是把属性和行为封装在一起的模板。1.1 类定义格式^ class 为定义类的关键字 Stack 为类的名字{} 中为类的主体注意类定义结束时后面分号不能省略。类体中内容称为类的成员类中的变量称为类的属性或成员变量类中的函数称为类的方法或者成员函数。^ 为了区分成员变量一般习惯成员变量加一个特殊标识如在成员变量前面或者后面加_或者 m 开头。^ C中 struct 也可以定义类C 兼容C中 struct 的用法同时 struct 升级成了类明显的变化是 struct 中可以定义函数一般情况下还是推荐使用 class 定义类。^ 定义在类面的成员函数默认为 inline。1.2 访问限定符^ C 一种实现封装的方式用类将对象的属性与方法结合在一起让对象更加完善通过访问权限选择性的将其接口提供给外部的用户使用。^ public 修饰的成员在类外可以直接被访问protected 和 private 修饰的成员在类外不能直接被访问。^ 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止如果后面没有访问限定符作用域就到 } 即类结束。^ class 定义成员没有被访问限定符修饰时默认为 private ,struct 默认为 public.^ 一般成员变量都会被限制为 private / protected ,需要给别人使用的成员函数则放为 public.1.3 类域^ 类定义了一个新的作用域类的所有成员都在类的作用域中在类体外定义成员时需要使用 作用域操作符指明成员属于哪个类域。^ 类域影响的是编译的插找规则。#includeiostream using namespace std; class Stack { public: // 成员函数 void Init(int n 4); private: // 成员变量 int* array; size_t capacity; size_t top; }; // 声明和定义分离需要指定类域 // 声明只说有 定义真正造出来 void Stack::Init(int n) { array (int*)malloc(sizeof(int) * n); if (nullptr array) { perror(malloc申请空间失败); return; } capacity n; top 0; } int main() { Stack st; st.Init(); return 0; }2. 实例化2.1 实例化概念^ 用类类型在物理内存中创建对象的过程称为类实例化出对象。^ 类是对象进行一种抽象描述是一个模型一样的东西限定了类有哪些成员变量这些成员变量只是声明没有分配空间用类实例化出对象时才会分配空间。^ 一个类可以实例化出多个对象实例化出的对象占用实际的物理空间存储类成员变量。打个比方类实例化出对象就像现实中使用建筑设计图建造出的房子类就像是设计图设计图规划了有多少个房间房间大小功能等但是并没有实体的建筑存在也不能住人用设计图修建出房子房子才能住人。同样类就像实际图一样不能存储数据实例化出的对象分配物理内存存储数据。#includeiostream using namespace std; class Date { public: void Init(int year, int month, int day) { _year year; _month month; _day day; } void print() { cout _year / _month / _day endlendl; } private: // 这里只是声明并没有开空间 int _year; int _month; int _day; }; int main() { // Date类实例化出对象 d1 和 d2 Date d1; Date d2; d1.Init(2026, 3, 9); d1.print(); d2.Init(2026, 4, 11); d2.print(); return 0; }2.2 对象大小内存对齐规则^ 第一个成员在与结构体偏移量为 0 的地址处^ 其他成员变量要对齐到某个数字对齐数的整数倍的地址处^ 注意对齐数 编译器默认的一个对齐数与该成员大小的较小值^ VS 默认的对齐数为 8^ 结构体总大小为最大对齐数所有变量类型最大者与默认对齐参数取最小的整数倍^ 如果嵌套了结构体的情况嵌套的结构体对齐到自己的最大对齐数的整数倍处结构体的整体大小就是所有最大对齐数含嵌套结构体的对齐数的整数倍#includeiostream using namespace std; class A { }; int main() { A a; cout sizeof(a) endl; return 0; }上面的程序运行后我们发现成员变量的 C 类对象的大小是 1这里给 1 个字节纯粹是为了占位标识对象存在。3. this 指针^ this 指针是一个指向当前对象自己的指针或引用。当成员变量和局部变量 / 参数重名时用 this 区分谁是对象自己的。^ 编译器编译后类的成员函数默认都会在形参第一个位置增加一个当前类类型的指针叫做 this 指针。比如 Date 类的 Init 的真实原型为void Init ( Data* const this, int year , int month , int day ).^ 类的成员函数中访问成员变量本质都是通过 this 指针访问^ C 规定不能在实参和形参的位置显示的写 this 指针但是可以在函数体内显示使用 this 指针。4. 类的默认成员函数默认成员函数就是用户没有显示实现编译器会自动生成的成员函数称为默认成员函数。6 个默认成员函数初始化和清理 构造函数主要完成初始化工作析构函数主要完成清理工作拷贝复制 拷贝构造是使用同类对象初始化创建对象赋值重载主要是把一个对象赋值给另一个对象取地址重载获取变量 / 对象的内存地址对一个对象直接用 会返回对象在内存中 的真实起始地址。5. 构造函数构造函数是特殊的成员函数需要注意的是构造函数虽然名称叫构造但是构造函数的主要任务并不是开空间创建对象而是对象实例化时初始化对象。构造函数的特点^ 函数名与类名相同^ 无返回值返回值啥都不需要给也不需要写 void)^ 对象实例化时系统会自动调用对应的构造函数^ 构造函数可以重载^ 如果类中没有显示定义构造函数则 C 编译器会自动生成一个无参的默认构造函数一旦用户显示定义编译器将不再生成^ 无参构造函数全缺省构造函数我们不写构造时编译器默认生成的构造函数都叫做默认构造函数。但是这三个函数有且只有一个存在不能同时存在。无参构造函数和全缺省构造函数虽然构成函数重载但是调用时会存在歧义^ 我们不写编译器默认生成的构造对内置类型成员变量的初始化没有要求也就是说是否初始化时不确定的看编译器。对于自定义类型成员变量要求调用这个成员变量的默认构造函数初始化C 把类型分成内置类型基本类型int / char / double / 指针和自定义类型class / struct 等关键字^ 实现构造函数时初始化成员变量主要使用函数体内赋值构造函数初始化还有一种方法就是初始化列表初始化列表的使用方式是以一个冒号开始接着是一个以逗号分隔的数据成员列表每个成员变量后面跟一个放在括号中的初始值或表达式。^ 每个成员变量在初始化列表中只能出现一次语法理解上初始化列表可以认为是每个成员变量定义初始化的地方。^ 引用成员变量const 成员变量没有默认构造的类类型变量必须放在初始化列表位置进行初始化否则会编译报错。^ C11 支持成员变量声明的位置给缺省值这个缺省值主要是给没有显示在初始化列表初始化的成员使用。^ 尽量使用初始化列表初始化因为那些你不在初始化列表初始化的成员也会走初始化列表如果这个成员在声明位置给了缺省值初始化列表会用这个缺省值初始化。如果没有给缺省值对于没有显示在初始化列表初始化的内置类型成员是否初始化取决于编译器。对于没有显示在初始化列表初始化的自定义类型成员会调用这个成员类型的默认构造函数如果没有默认构造会编译错误^ 初始化列表中按照成员变量在类中声明顺序进行初始化跟成员在初始化列表出现的先后顺序无关。建议声明顺序和初始化列表顺序保持一致。6. 析构函数析构函数与构造函数功能相反析构函数不是完成对对象本身的销毁比如局部对象是存在栈帧的函数结束栈帧销毁也就释放了C 规定对象在销毁时会自动调用析构函数完成对象中资源的清理释放工作。析构函数的特点^ 析构函数名是在类名前加上字符 ~。^ 无参数无返回值也不需要加 void)。^ 一个类只能有一个析构函数若未显示定义系统会自动生成默认的析构函数。^ 对象生命周期结束时系统会自动调用析构函数。^ 跟析构函数类似我们不写编译器自动生成的析构函数对内置类型成员不做处理自定类型成员会调用它的析构函数。^ 如果类中没有申请资源是析构函数可以不写直接使用编译器生成的默认析构函数但是有资源申请时一定要自己写析构否则会造成资源泄露。^ 一个局部域的多个对象C 规定后定义的先析构。7. 拷贝构造函数如果一个构造函数的第一个参数是自身类类型的引用且任何额外的参数都有默认值则此构造函数也叫做拷贝构造函数也就是说拷贝构造是一个特殊的构造函数。拷贝构造函数的特点^ 拷贝构造函数是构造函数的一个重载。^ 拷贝构造函数的第一个参数必须是类类型对象的引用使用传值方式编译器直接报错因为语法逻辑上会引发无穷递归调用。拷贝构造函数也可以多个参数但是第一个参数必须是类类型对象的引用后面的参数必须有缺省值。^ C 规定自定义类型对象进行拷贝行为必须调用拷贝构造所以这里自定义类型传值传参和传值返回值都会调用拷贝构造完成。^ 若未显示定义构造编译器会自动生成拷贝构造函数。自动生成的拷贝构造对内置类型成员变量会完成值拷贝 / 浅拷贝对自定义类型成员变量会调用它的拷贝构造^ 如果一个类显示实现了析构并释放资源那么它就需要显示写拷贝构造否则就不需要。^ 传值返回会产生一个临时对象调用拷贝构造传值引用返回返回的是返回对象的别名没有产生拷贝。但是如果返回一个对象是一个当前函数局部域的局部对象函数结束就销毁了那么使用返回是有问题的这是的引用相当于一个野引用类似一个野指针一样。传引用返回可以减少拷贝但是一定要确保返回对象在当前函数结束后还在才能引用返回。8. 赋值运算符重载8.1 运算符重载^ 当运算符被用于类类型的对象是C 允许我们通过运算符重载的形式指定新的定义。C 规定类类型对象使用运算符时必须转换成调用对应运算符重载若没有对应的运算符重载则会编译报错。^ 运算符重载是具有特殊名字的函数它的名字是由 operator 和后面要定义的运算符共同构成。和其他函数一样它也具有其返会类型和参数列表以及函数体。^ 重载运算符函数的参数个数和该运算符作用的运算对象数量一些多。一元运算符有一个参数二元运算符有两个参数二元运算符的左侧运算对象传给第一个参数右侧运算对象传给第二个参数。^ 如果一个重载运算符函数是成员函数则它的第一个运算对象默认传给隐式的 this 指针因此运算符重载作为成员函数时参数比运算对象少一个。^ 运算符重载以后其优先级和结核性与对于的内置类型运算符保持一致。^ ( .* :: sizeof ?: . ) 以上 5 个运算符不能重载。^ 重载操作符至少有一个类类型参数不能通过运算符重载改变内置类型对象的 含义如 int operator (int x, int y)^ 重载 运算符时有前置 和后置 运算符重载函数名都是 operator.无法很好的区分。C 规定后置 重载时增加一个 int 形参跟前置 构成函数重载。8.2 赋值运算符重载赋值运算符重载是一个默认成员函数用于完成两个已经存在的对象直接的拷贝赋值这里要注意跟拷贝构造区分拷贝构造用于一个对象拷贝初始化给另一个要创建的对象。赋值运算符重载的特点^ 赋值运算符重载是一个运算符重载规定必须重载为成员函数。赋值运算重载的参数建议写成 const 当前类类型引用否则会传值会有拷贝。^ 有返回值且建议写成当前类类似引用引用返回可以提高效率有返回值目的是为了支持连续赋值场景。^ 没有显示实现时编译器会自动生成一个默认赋值运算符重载默认赋值运算符重载行为跟默认拷贝构造函数类似对内置类型成员变量会完成值拷贝 / 浅拷贝对自定义类型成员变量会调用它的赋值重载函数。^ 如果一个类显示实现了析构并释放资源那么他就需要显示写赋值运算符重载否则就不需要。9. 取地址运算符重载9.1 const 成员函数^ 将 const 修饰的成员函数称为 const 成员函数 cosnt 修饰成员函数放大成员函数参数列表的后面。^ cosnt 实际修饰该成员函数隐含的 this 指针表面在该成员函数中不能对类的任何成员进行修改。#includeiostream using namespace std; class Date { public: Date(int year 1, int month 1, int day 1) { _year year; _month month; _day day; } void print()const { cout _year - _month - _day endl; } private: int _year; int _month; int _day; }; int main() { // 这里非 const 对象也可以调用 cosnt 成员函数是一种权限的缩小 Date d1(2025, 3, 12); d1.print(); const Date d2(2025, 4, 12); d2.print(); return 0; }10. static 成员^ 用 static 修饰的成员变量称之为静态成员变量静态成员变量定要在类外面初始化。^ 静态成员变量为所有类对象所共享不属于某个具体的对象不存在对象中存放在静态区。^ 用 static 修饰的成员函数称之为静态成员函数静态成员函数没有 this 指针。^ 静态成员函数中可以访问其他的静态成员但是不能访问非静态的因为没有 this 指针。^ 非静态的成员函数可以访问任意的静态成员变量和静态成员函数^ 突破类域就可以访问静态成员可以通过 类名静态成员 或者 对象. 静态成员 来访问静态成员变量和静态成员函数^ 静态成员也是类的成员受 public , protected , private 访问限定符的限制。^ 静态成员变量不能在声明位置给缺省值初始化因为缺省值是个构造函数初始化列表的静态成员变量不属于某个对象不走构造函数初始化列表11. 友元^ 友元提供了一种突破列访问限定符封装的方式友元分为友元函数和友元类在函数声明或者类声明的前面加 friend 并且比啊友元声明放到一个类的里面。^ 外部友元函数可访问类的私有和报复成员友元函数仅仅是一种声明它不是类的成员函数。^ 友元函可以在类定义的任何地方声明不受类访问限定符限制。^ 一个函数可以是多个类的友元函数。^ 友元类中的成员函数都可以是另一个类的友元函数都可以访问另一个类中的私有和保护成员。^ 友元类的关系是单向的不具有交换性比如A类是B类的友元但是B类不是A类的友元。^ 友元类关系不能传递如果A是B的友元B是C的友元但A不是C的友元。^ 有时提供了便利但是友元会增加耦合度破坏了封装所以友元不宜多用。12. 内部类^ 如果一个类定义在另一个类的内部这个内部类就叫做内部类。内部类是一个独立的类跟定义在全局相比它只是受外部类类域限制和访问限定符限制所以外部类定义的对象不包含内部类。^ 内部类默认是外部类的友元类。^ 内部类本质也是一种封装当A类跟B 类紧密关联A类实现出来主要就是给B类使用那么可以考虑把A类设计为B的内部类如果放到 private / protected 位置那么A类就是B类的专属内布列其他地方都用不了。13. 匿名对象^ 用类型实参定义出来的对象叫做匿名对象相比之前我们定义的类型对象名实参定义出来了的叫有名对象。^ 匿名对象生命周期只在当前一行一般临时定义一个对象当前用一下即可就可以定义匿名对象。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2411184.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!