当前位置: 首页 > news >正文

网站建设最好的公司哪家好国贸网站建设公司

网站建设最好的公司哪家好,国贸网站建设公司,优化提升,个人网站设计作业类 C语言结构体中只能定义变量. 在C中#xff0c;结构体内不仅可以定义变量#xff0c;也可以定义函数。 //c语言 typedef struct ListNode {int val;struct ListNode* next; }LTN; //c struct ListNode {int val;//c中可以直接用这个#xff0c;不用加structListNode* next…类 C语言结构体中只能定义变量. 在C中结构体内不仅可以定义变量也可以定义函数。 //c语言 typedef struct ListNode {int val;struct ListNode* next; }LTN; //c struct ListNode {int val;//c中可以直接用这个不用加structListNode* next; };比如 之前在数据结构初阶中用C语言方式实现的栈结构体中只能定义变量现在以C方式实现。 //C用类模拟实现栈 struct Stack {//可以定义函数void Init(int n 4){a (int*)malloc(sizeof(int) * n);if (nullptr a){perror(malloc fail);exit(-1);}capacity n;size 0;}void Push(int x){//...a[size] x;}//成员变量int* a;int size;int capacity; };int main() {Stack st;//就像访问结构体成员一样访问函数st.Init();st.Push(1);st.Push(2);st.Push(3);st.Push(4);return 0; }一、类的定义 在C中虽然struct 可以作为定义类的 关键字但是 一般使用 class 作为定义类的关键字{} 中的内容成为 类的主体 类体中内容称为类的成员 类中的变量称为类的属性或成员变量; 类中的函数称为类的方法或者 成员函数。 class className { // 类体由成员函数和成员变量组成 }; // 一定要注意后面的分号类的两种定义方式 1.声明和定义全部放在类体中 注意声明和定义全部放在类体中 需注意成员函数如果在类中定义编译器可能会将其当成内联函数处理。 其实就是像我们上面这种。 struct Stack {//可以定义函数void Init(int n 4){a (int*)malloc(sizeof(int) * n);if (nullptr a){perror(malloc fail);exit(-1);}capacity n;size 0;}void Push(int x){//...a[size] x;}//成员变量int* a;int size;int capacity; };int main() {Stack st;//就像访问结构体成员一样访问函数st.Init();st.Push(1);st.Push(2);st.Push(3);st.Push(4);return 0; }2.类声明放在.h文件中成员函数定义放在.cpp文件中 这个其实就是声明和定义分离我们需要注意的一点就是 成员函数名前需要加类名::,因为我们需要找到这个域从这个域中找到这个函数 stack.h的内容 struct Stack {//可以定义函数void Init(int n 4);void Push(int x);int* a;int size;int capacity; };stack.c的内容 //缺省参数一般在声明处定义 void Stack::Init(int n ) {a (int*)malloc(sizeof(int) * n);if (nullptr a){perror(malloc fail);exit(-1);}capacity n;size 0; } void Stack::Push(int x) {//...a[size] x; }二、访问限定符 有的同学尝试用class代替struct发现遍不过去其实就是我们的访问限定符的作用 访问限定符的种类 【访问限定符说明】 public修饰的成员在类外可以直接被访问protected和private修饰的成员在类外不能直接被访问(此处protected和private是类似的)访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止如果后面没有访问限定符作用域就到 } 即类结束。class的默认访问权限为privatestruct为public(因为struct要兼容C) 举例说明,如果在声明和定义分离一般在声明中设置访问限定符 class Stack {//一般我们的成员函数设定为公有 public:void Init(int n 4);void Push(int x);//我们的成员变量设定为私有 private:int* _a;int _size;int _capacity; };在成员变量的命名习惯在成员变量前加一个_,防止当我的成员函数用相同的名字的变量起冲突。 三、封装 将数据和操作数据的方法进行有机结合隐藏对象的属性和实现细节仅对外公开接口来和对象进行交互。 封装本质上是一种管理让用户更方便使用类。 四、类的实例化 在理解类的实例化我们需要先理解另一个知识点。 在我们定义类的时候我们知道他的内容是成员函数和成员变量。 这时我提一个问题我们的成员变量他是声明还是定义到底开没有空间。 答案其实我们定义的成员变量是声明。并且他没有开辟空间。 其实我们可以这样理解我们的类就相当于一个建房子的一个图纸只是定义了我们怎么去建造房子并没有建好房子而建房子这个就叫做实例化。 类的实例化其实是这样的 class Data { public:void Init(int year, int month, int day){_year year;_month month;_day day;} private://这个只是声明并没有开辟空间int _year;int _month;int _day;};int main() {//这个其实就是我们类的实例化//开辟空间Data d1;Data d2; }五、类对象的大小 什们是类的对象在我们类的实例化的时候我们定义类的内容就是我们的对象。 看上面代码就是我们的d1和d2. 类中包括成员函数和成员变量 但是我们的规定我们类的对象只包括类的成员变量不包括成员函数。 怎么计算类对象的大小所以我们计算类对象的大小就只计算我们的成员变量并且计算成员变量的时候我们要像结构体一样考虑内存对齐。 为什们类的对象不包括成员函数我们知道每个对象的成员变量是不一样的需要独立存储。 但是我们每个对象成员函数都是一样的我们放到公共区域这个区域叫做代码段 看下图自己算几个, 六、this指针 我们会不会产生一个问题当我们在使用类的函数的时候每个类的对象都有可能会用这个函数并且传的参数类型和数量都一样那么编译器是怎么分清楚那个是那个数据是那个对象的。 其实编译器会自己做一件事情这件事情我们做不了只有编译器可以做具体怎么做看下图。 this指针存在哪里存放在栈中因为它是局部变量并且他还是隐含形参。 但是在vs下面是通过ecx寄存器 练习题 class Data { public:void Init(int year, int month, int day){_year year;_month month;_day day;}void func(){cout this endl;cout func endl;}//这个只是声明并没有开辟空间int _year;int _month;int _day;};int main() {Data* ptr nullptr;//下面代码会不会报错ptr-func(); //正常运行ptr-Init(2022, 3, 2); //报错(*ptr).func(); //正常运行ptr-_year;//运行崩溃 }对于第一个ptr-func()为什们正常运行好多人以为是崩溃。 首先我们知道ptr是我们对象的指针,首先我们要明白一点不是见了-就是解引用。 对于对象他包括的是成员变量但是我们的成员函数并不属于我们的对象它存在于公共的区域代码段而ptr的作用就是带我们找到这个域并将这个这个ptr传给成员函数的this也就是空指针在我们这个成员函数没有发生空指针的解引用只是将这个空指针this打印出来。 对于第二个我们再找到对于的成员函数并不会崩溃但是在这个函数中发生了空指针解引用所以才会崩溃。 有的同学会问哪里有空指针的解引用大家看这个代码_year year;,我们通过上面的学习我们怎么知道_year是那个对象的是用this指针找到的。所以就在这里发生了空指针的解引用。 对于第三个其实原理跟第一个原理一样他的作用就是带我们找到这个域并将this传个成员函数。 对于第四个他直接崩溃因为_year是成员变量存在于对象中而直接就是空指针的解引用. 类的6个默认成员函数 默认成员函数的特点我们如果不写编译器会自动生成一个默认的但是如果我们实现了编译器就不自动生成了。 一、构造函数 为什们要有构造函数其实这个问题很简单假如我们要写一个栈我们每次在使用前我们都要初始化但是如果我们忘了怎么办呢突然有一次没有写这个初始化那么我们程序就会崩溃 所以既然我们每次使用都要调用初始化为什们不交给编译器来做呢所以构造函数应运而生。 构造函数是特殊的成员函数需要注意的是构造函数虽然名称叫构造但是构造函数的主要任务并不是开空间创建对象而是初始化对象。 构造函数是一个特殊的成员函数名字与类名相同,创建类类型对象时由编译器自动调用以保证每个数据成员都有 一个合适的初始值并且在对象整个生命周期内只调用一次。 Ⅰ、构造函数的特征 函数名与类名相同。无返回值。(注意并不是void而就是不写返回值)对象实例化时编译器自动调用对应的构造函数。构造函数可以重载。一个类可以有多个构造函数有多种初始化方式 Ⅱ、写一个构造函数 struct Stack { public://构造函数Stack(){_a nullptr;_size _capacity 0;}//构造函数的函数重载Stack(int n){_a (int*)malloc(sizeof(int) * n);if (nullptr _a){perror(malloc fail);exit(-1);}_capacity n;_size 0;}void Push(int x){//..._a[_size] x;} private:int* _a;int _size;int _capacity; }; int main() {//构造函数无参Stack st;//Stack st(); 无参不能这样传参就不用加括号//构造函数有参数Stack st1(4);//就像访问结构体成员一样访问函数st.Push(1);st.Push(2);st.Push(3);st.Push(4);return 0; }Ⅲ、默认构造函数 我们在上面六个默认成员的特点上面写了 如果自己没有写构造函数系统会自动生成一个默认构造函数。 C 的类型 内置类型。编译器本身带的int/char/double/指针 自定义类型。class/struct定义的 编译器生成的默认构造函数 内置类型成员不做处理。 自定义类型的成员会去调用自定义类型的默认构造不用传参数的构造 ①内置类型的默认构造函数 看下面的例子 struct Data { public:void Print(){cout _year 年 _month 月 _day 日 endl;} private:int _year;int _month;int _day; };int main() {Data d1;d1.Print();return 0; }我们发现打印的都是随机值其实他有默认构造函数但是因为是内置类型所以并没有做处理。 所以当我们用这种日期的类的时候我们不要用这种默认构造函数。 其实这是一个bug为什们内置类型没有做处理这是有问题的。这是设计者的失误。所以设计者打了一个补丁。 内置类型的默认构造函数补丁 既然我们不能改了。 那么我们在声明成员变量的时候用缺省参数就可以很好的解决这个问题。 注意这个并不是初始化成员变量。千万不要搞混。 看下面的例子。 struct Data { public:void Print(){cout _year 年 _month 月 _day 日 endl;} private://声明位置写初始化int _year1;int _month1;int _day1; };int main() {Data d1;d1.Print();return 0; }②自定义类型的默认构造函数 struct Stack { public://构造函数Stack(){_a nullptr;_size _capacity 0;}//构造函数的函数重载Stack(int n){_a (int*)malloc(sizeof(int) * n);if (nullptr _a){perror(malloc fail);exit(-1);}_capacity n;_size 0;}void Push(int x){//..._a[_size] x;}//析构函数~Stack(){free(_a);_a nullptr;_size _capacity 0;} private:int* _a;int _size;int _capacity; };class MyQueue { public:// 默认生成构造函数对自定义类型成员会调用他的默认构造函数// 默认生成析构函数对自定义类型成员会调用他的析构函数void push(int x) {}//....Stack _pushST;Stack _popST;int _size 0; };int main() {MyQueue q;return 0; }Ⅳ、默认构造函数注意 无参的构造函数和全缺省的构造函数都称为默认构造函数并且默认构造函数只能有一个。 注意无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数都可以认为是默认构造函数 不传参数就可以调用构造函数 一般建议每个类都提供要给默认构造函数。 二、析构函数 为什们要有析构函数跟构造函数一样析构函数解决初始化的问题但是假设我们写一个栈不仅对于他来说要初始化更要销毁栈清理资源。 但是如果我们忘了这件事我们的程序岂不是要出大问题。 这时我们的析构函数就排上用场了 Ⅰ、析构函数的特性 析构函数与构造函数功能相反析构函数不是完成对对象本身的销毁局部对象销毁工作是由编译器完成的。 而对象在销毁时会自动调用析构函数完成对象中资源的清理工作。 析构函数是特殊的成员函数其特征如下 析构函数名是在类名前加上字符 ~。(~就是取反的意思意思就是构造函数取反)无参数无返回值类型。一个类只能有一个析构函数。若未显式定义系统会自动生成默认的析构函数。注意析构函数不能重载对象生命周期结束时C编译系统系统自动调用析构函数。 Ⅱ、默认析构函数 跟构造函数一样就不多介绍了 默认生成构造函数对自定义类型成员会调用他的默认构造函数 默认生成析构函数 对自定义类型成员会调用他的析构函数 内置类型成员不做处理。 Ⅲ、写一个析构函数 struct Stack { public://构造函数Stack(){_a nullptr;_size _capacity 0;}//构造函数的函数重载Stack(int n){_a (int*)malloc(sizeof(int) * n);if (nullptr _a){perror(malloc fail);exit(-1);}_capacity n;_size 0;}void Push(int x){//..._a[_size] x;}//析构函数~Stack(){free(_a);_a nullptr;_size _capacity 0;} private:int* _a;int _size;int _capacity; }; int main() {//构造函数无参//Stack st;//Stack st(); 无参不能这样传参就不用加括号//构造函数有参数Stack st(4);//就像访问结构体成员一样访问函数st.Push(1);st.Push(2);st.Push(3);st.Push(4);//看到return,就自动调用析构函数return 0; }三、拷贝构造 Ⅰ、拷贝构造的特征 拷贝构造函数是构造函数的一个重载形式。拷贝构造函数的参数只有一个且必须是类类型对象的引用使用传值方式编译器直接报错因为会引发无穷递归调用。 ①为什们拷贝构造的参数必须是类对象的引用 class Date { public:Date(int year 1900, int month 1, int day 1){_year year;_month month;_day day;}// 直接传内置类型这是不被允许的被禁止的//下面解释为什们不能这么做的原因Date(Date d){_year d._year;_month d._month;_day d._day;} private:int _year;int _month;int _day; }; int main() {Date d1;Date d2(d1);//拷贝构造的另一种写法Data d3 d1;return 0; }其实也是很容易理解的对于编译器的内置类型编译器会傻瓜式的进行浅拷贝就是按照字节拷贝。 但是如果对于自定义类型如果我们进行浅拷贝可能会发生一些问题。 举个例子 假如我们的自定义类型 栈1中有一个指针现在我们需要拷贝到另一个栈2但是因为浅拷贝只将栈1指针的4个字节传了过来并没有开辟空间所以两个栈的指针指向同意块空间。这是非常大的问题。 所以我们不能直接用自定义类型当参数。 //正确的拷贝构造应该用引用 Date(const Date d){_year d._year;_month d._month;_day d._day;}但是如果我们用引用传参的时候Date d2(d1); d是d1的别名我们直接将d1的值赋给d2.这也是比较容易理解 ②为什们不用类对象的引用会发生无穷递归 我们看上面的代码要进行类的实例化 Date d2(d1);我们就要调用对应的构造函数Date(Date d)但是我们发现构造函数的参数还是一个拷贝构造然后他的参数就一直是这个就会造成无穷递归。 我们的拷贝构造无限递归是因为在调用拷贝构造的时候参数如果不是引用传参形成一个新的拷贝构造而调用新的拷贝构造又要传参就形成了一个死循环。 Ⅱ、默认拷贝构造函数 若未显式定义编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝这种拷贝叫做浅拷贝或者值拷贝。 我们在上面已经介绍了什们叫做浅拷贝就是按照字节进行拷贝 对于内置类型这种默认生成的拷贝构造是没有问题的。 但是对于自定义类型如果按照拷贝构造按照字节进行拷贝如果有指针就会将这两个指向同一块空间会造成很大的问题。 为什们自定义类型不能用默认拷贝构造指向同一块空间 插入删除数据会互相影响 析构两次程序崩溃 默认生成的拷贝构造和赋值重载a、内置类型完成浅拷贝/值拷贝 b、自定义类型去调用这个成员的拷贝构造/赋值重载 什们时候情况下需要实现拷贝构造 自己实现了析构函数释放空间就需要实现拷贝构造。 typedef int DataType; class Stack { public:Stack(size_t capacity 10){cout Stack(size_t capacity 10) endl;_array (DataType*)malloc(capacity * sizeof(DataType));if (nullptr _array){perror(malloc申请空间失败);exit(-1);}_size 0;_capacity capacity;}void Push(const DataType data){// CheckCapacity();_array[_size] data;_size;}Stack(const Stack st){cout Stack(const Stack st) endl;_array (DataType*)malloc(sizeof(DataType)*st._capacity);if (nullptr _array){perror(malloc申请空间失败);exit(-1);}memcpy(_array, st._array, sizeof(DataType)*st._size);_size st._size;_capacity st._capacity;}~Stack(){cout ~Stack() endl;if (_array){free(_array);_array nullptr;_capacity 0;_size 0;}}private:DataType *_array;size_t _size;size_t _capacity; };class MyQueue { public:// 默认生成构造// 默认生成析构// 默认生成拷贝构造private:Stack _pushST;Stack _popST;int _size 0; };int main() {MyQueue q1;MyQueue q2(q1);return 0; }四、运算符重载 C为了增强代码的可读性引入了运算符重载运算符重载是具有特殊函数名的函数也具有其返回值类型函数名字以及参数列表其返回值类型与参数列表与普通的函数类似。 函数名字为关键字operator后面接需要重载的运算符符号。 函数原型返回值类型 operator操作符(参数列表) 例子现在我们有两个日期类我们想比较一下判断一下日期是否相等 我们之前就是写一个函数然后将两个参数传上去然后我们在函数内比较。但是呢我们如果是两个内置类型我们直接就可以用运算符比较编译器就可以比较。 但是我们的自定义类型却不可以里面的东西比较复杂所以我们得写一个函数自己去才可以解决。 运算符重载的意义自定义类型的对象可以直接用运算符比较。 Ⅰ、将运算符重载放到全局 日期类比较日期是否相等 class Date { public:Date(int year 1900, int month 1, int day 1){_year year;_month month;_day day;}private:int _year;int _month;int _day; };//第一个参数是左操作数第二个参数是右操作数 //这个是特别注意的。 bool operator(const Date d1,const Dated2) {return d1._year d2._year d1._month d2._month d1._day d2._day; }int main() {Date d1(2023, 2, 11);Date d2(2023, 3, 11);operator(d1, d2);d1 d2;//转换成上面这个return 0; }我们注意现在这个程序是有问题的因为我们类的成员变量是私有的我们不能访问现在最简单的操作就是将私有设置为共有这样我们就可以判断了。 这个函数原型其实本质就是一个函数只不过我们的函数名设置成了我们规定的这种。 所以我们在使用的时候其实可以跟我们使用函数一样也可以直接用对象进行比较。 operator(d1, d2);或 d1 d2; Ⅱ、将运算符重载放到类中 当我们放到全局中我们的类的成员变量被我们设置成共有的收到访问限定符的限制为了解决这个问题。 我们将这个放到类当中就可以解决这个问题。 class Date { public:Date(int year 1900, int month 1, int day 1){_year year;_month month;_day day;}bool operator(const Date d){return _year d._year _month d._month _day d._day;} private:int _year;int _month;int _day; };int main() {Date d1(2023, 2, 11);Date d2(2023, 3, 11);d1.operator(d2);d1 d2;return 0; }我们发现跟我们想的不一样我们在类中的操作数这么就变成一个了其实是有一个隐藏的操作数传上去了就是this而对于这个隐藏操作数就是d1所以我们只需要传d2就行了。 然后在函数传参的时候我们也有两种方式d1.operator(d2); 和 d1 d2; 他的大于小于就不多写了其实跟他一样。 就看下面的代码 bool operator(const Date d){return _year d._year _month d._month _day d._day;}bool operator (const Date d){if (_year d._year){return true;}else if (_year d._year _month d._month){return true;}else if (_year d._year _month d._month _dayd._day){return true;}else{return false;}}//其他的就可以直接复用bool operator (const Date d){return *this d || *this d;}bool operator (const Date d){return !(*this d);}bool operator (const Date d){return !(*this d );}bool operator ! (const Date d){return !(*this d);} Ⅲ、 赋值运算符 (1)赋值运算符重载格式 参数类型const T传递引用可以提高传参效率返回值类型T返回引用可以提高返回的效率有返回值目的是为了支持连续赋值检测是否自己给自己赋值返回*this 要复合连续赋值的含义 (2)赋值运算符只能重载成类的成员函数不能重载成全局函数. (3) 用户没有显式实现时编译器会生成一个默认赋值运算符重载以值的方式逐字节拷贝。 注 意内置类型成员变量是直接赋值的而自定义类型成员变量需要调用对应类的赋值运算符 重载完成赋值。 //d1 d2//带返回值是为了连续赋值。Date operator(const Date d){//防止自己给自己赋值if (this ! d){_year d._year;_month d._month;_day d._day;}return *this;}首先我们解释一点我们传参可以不用传引用他不会像我们拷贝构造函数无穷递归 因为我们在调用这个的时候函数进行拷贝构造然后就直接进行赋值了。但是我们的拷贝构造是因为在调用拷贝构造的时候参数如果不是引用传参形成一个新的拷贝构造而调用新的拷贝构造又要传参就形成了一个死循环。 Ⅳ、运算符重载注意 不能通过连接其他符号来创建新的操作符比如operator重载操作符必须有一个类类型参数用于内置类型的运算符其含义不能改变例如内置的整型不能改变其含义作为类成员函数重载时其形参看起来比操作数数目少1因为成员函数的第一个参数为隐藏的this。.* :: sizeof ?: . 注意以上5个运算符不能重载。这个经常在笔试选择题中出 Ⅴ、用运算符重载写一个输入流和输出流 ①流插入 我们C中我们输入流和输出流就是用cin和cout。 他们最大的特点就是能够自动识别类型。 我们平常用的cind和coutd这个其实就是运算符重载和就是运算符。 我们要想看明白下面的内容理解这个是必要的。 cin和cout其实就是类的对象。 同时大家需要知道ostream是cout对象的类。 istream是cin对象的类 为什们能够支持自动识别类型 支持自动识别是因为库里面已经帮我们写了并且支持函数重载。当我们写不同类型的时候我们就会调用不同的函数。 所以我们写的cout d其实就是cout.operator (int),通过你传的参去识别类型。 如果我们想要输入输出一个自定义类型但是对于自定义类型来说编译器并不能自动识别所以需要我们自己用运算符重载一个。 //输出流,放到我们上面的日期类中。 void Date::operator(ostream out) {out _year 年 _month 月 _day 日 endl; }但是我们使用的时候对于我们写的这种类型不能像cout d这样使用这样会报错。d是我们日期类的对象 利用我们现有的知识对于运算符重载规定 第一个参数是左操作数第二个参数是右操作数 如果是在类中那么传的时候会将第一个参数省略传的是this指针 对于我们这个函数this指的是d第二个参数才是cout。 但是如果cout d这样写传第一个参数就是cout,第二个参数是d。 所以我们应该这样写。 d.operator(cout);也可以这样d1 cout; 但是这样写跟我们想的完全不一样。 所以我们这时可以不用将我们的运算符重载放到我们的类中。 可以将我们的运算符重载放到全局。 按照需要的参数顺序传参就没有this指针了。 具体传参为 operator(cout, d1);或 cout d1; 但是可能面对的问题就是不能访问私有的成员变量。这个其实有办法解决。 //加返回值是为了实现连续输出 //为了实现这种场景 cout d1 d2 endl; //定义 ostream operator(ostream out,const Date d) {out _year 年 _month 月 _day 日 endl;return out; }访问私有成员变量的方法友元函数 具体做法就是将我们这个函数的声明放到我们的类中在前面加上friend。 下面会详细介绍如果不懂可直接跳到下面的友元函数。 //声明 class Date {//友元函数的声明friend ostream operator(ostream out, const Date d); public://成员函数 private:int _year;int _month;int _day; }; //在类外面声明ostream operator(ostream out, const Date d);注意有两个声明。 我们将这个运算符重载放到了全局中我们就再全局中声明也就是类的外面。 但是为了能够访问类中的成员函数在类中定义友元函数。 ②流提取 跟上面一样将运算符重载放到全局中。 并且有返回值是为了能够多重打印。 cout d1 d2 endl; 流提取在类中友元函数的声明 friend istream operator(istream in,Date d);流提取的运算符重载 istream operator(istream in, Date d) {in d._year d._month d._day;return in; }测试 Date d;cin d;cout d;③流提取和流插入的优化 因为流插入和流提取的运算符重载都很简单所以我们可以将他们变成内联函数然后都放到头文件中。 内联函数声明和定义都必须放在同文件中不能将声明和定义分开。 class Date {friend ostream operator(ostream out, const Date d);friend istream operator(istream in, Date d); public:private:int _year;int _month;int _day; }; inline ostream operator(ostream out, const Date d) {out d._year 年 d._month 月 d._day 日 endl;return out; } inline istream operator(istream in, Date d) {in d._year d._month d._day;return in; }Ⅵ、 为什们流插入流提取 我们在C语言中我们可以用printf既可以打印内置类型也可以打印自定义类型。 但是在C中我们用printf确实可以打印内置类型但是我们对于自定义类型也就是类他的成员变量是私有我们用printf很难突破访问限定符的限制所以我们就不能用printf 通过上面的自己构造自定义类型的流插入运算符重载 发现我们可以按照一定的规则重载运算符和。 我们的自定义类型就能通过coutcin打印并且可以按照自己想要的方式打印。 五、六、取地址及const取地址操作符重载 这两个默认成员函数一般不用重新定义编译器默认会生成。 我们自己实现取地址及const取地址操作符重载 using namespace std;class A { public:A* operator(){return this;}const A* operator() const{return this;} private:int _a 10; };int main() {const A aa;A x;cout x endl;cout aa endl;return 0; }所以我们就不需要写我们直接用他们的默认成员函数。 类和对象的其他小知识点 一、 const成员 我们先看一个例子 class A { public:void Print(){cout _a endl;} private:int _a 10; };int main() {const A aa;aa.Print();return 0; }我们的程序出现错误。其实这涉及到一个问题权限的放到。 我们定义了一个对象aa但是我们把他设置为了const,我们在实例化对象的时候会调用构造函数我们传一个隐藏参数this指向aa但是*this的类型却不是const所以造成了权限的放大。 所以要解决这个问题就是将this设置为const void Print() const规定const修饰*this.注意修饰的并不是this class A { public:void Print() const{cout _a endl;} private:int _a 10; };int main() {const A aa;aa.Print();return 0; }所以我们以后内部不改变成员变量的成员函数就用const修饰const对象和普通对象都可以调用。 二、用运算符重载改变数组 class A { public:void Print() const{cout _a endl;}int operator[](int i){assert(i 10);return _a[i];}//函数重载const int operator[](int i) const{assert(i 10);return _a[i];} private:int _a[10]; };int main() {A x;for (int i 0; i 10; i){x[i] i * 2;}for (int i 0; i 10; i){cout x[i] ;}return 0; }三、构造函数的初始化列表 我们有两种初始化的方式 Ⅰ、第一种是构造函数体赋值(不相当于初始化) 虽然下面构造函数调用之后对象中已经有了一个初始值但是不能将其称为对对象中成员变量的初始化构造函数体中的语句只能将其称为赋初值而不能称作初始化。因为初始化只能初始化一次而构造函数体内可以多次赋值。 class Date { public:Date(int year, int month, int day) {_year year; _month month; _day day;} private://声明int _year;int _month;int _day; };int main() {Date a;//对象整体的定义 }Ⅱ、初始化列表 ①引出初始化列表 我们知道对象实例化的时候Date a;是对象整体的定义。 这时就会有一个问题每个成员什们时候定义的呢 这时有人就会说不就是在整体定义的时候就将其成员定义了吗 其实话说是这样说但是类好像并不是。 我举个例子 int main() {//判断是否正确const int i;return 0; }我们看上面的代码我们看他是典型的错误。 i被const修饰所以它具有常性在定义完之后就不可在修改了如果你不在定义的时候初始化那么以后他将没有机会初始化了。 结论const修饰的变量必须在定义的时候初始化。 那我们看下面的类 class A { public:private://这个只是声明并不是定义const int _x; };int main() {A a;//对象整体的定义return 0; }首先上面这个程序是错误的。 对于这个类成员变量是常变量我们必须在定义的时候初始化但是声明也不能初始化。对象整体的定义的时候也没有初始化。 所以就抛出了下面这个问题。 每个成员什们时候定义的呢 第一种其实我们在上面默认构造函数的时候说了他对内置类型不做处理其实是一个bug在C11这时打了一个补丁。 用在声明的时候用缺省参数可以很好的解决问题。在调用默认构造函数的时候就用这个缺省参数定义了。具体看下面代码。 class A { public:private://用缺省参数const int _x 1; };int main() {A a;//对象整体的定义return 0; }但是上面这种方法是在2011年才打的补丁C在98年就出来并且就有这个问题那么在这段期间是这么解决的呢 其实就是用到了初始化列表。 ②怎么用初始化列表 因为上面的问题所以必须给每个成员变量找一个定义的位置否则就会像const的变量就没有办法初始化。 对于对象来说初始化列表是成员变量定义的地方。 初始化列表的位置在构造函数里面具体位置看下面代码。 初始化列表形式以一个冒号开始接着是一个以逗号分隔的数据成员列表每个成员变量后面跟 一个放在括号中的初始值或表达式。 class Date { public:Date(int year, int month, int day): _year(year), _month(month), _day(day){} private:int _year;int _month;int _day; };③初始化列表的规定 那个对象调用构造函数初始化列表是它所有成员变量定义的位置。不管是否在初始化列表写编译器的每个变量都会走初始化列表在初始化列表定义和初始化。 做一下下面的练习题 class A { public:A():_x(1),_a2(1){_a1;_a2;} private://用缺省参数int _a1 1;int _a2 2;const int _x; };int main() {A a;//对象整体的定义return 0; }答案是a12a22x1。有了这个例子我相信大家肯定可以看懂第二点。 每个初始化列表只能出现一次。 class A { public:A():_x(1),_a2(1),_a2(2)//错误的代码不能出现两次只能出现一次。{_a1;_a2;} private://用缺省参数int _a1 1;int _a2 2;const int _x; }; 有三类必须在初始化列表初始化 ①引用成员变量 ②const成员变量 ③自定义类型成员(且该类没有默认构造函数时) 对于前两个还比较好理解引用如果不初始化就会产生一个问题他是谁的别名这个就非常奇怪。 下来我们详细看一下为什们第三个不可以。 class B { public:B():b(1){} private:int b; };class A { public:A():_x(1),_a2(1)//引用的初始化,ret(_a1) //表示ret是_a1的引用。{_a1;_a2;} private://用缺省参数int _a1 1;int _a2 2;const int _x;int ret;B _bb; };首先上面这个程序没有任何问题 这时有人会问为什们没有在初始化列表中让自定义类型初始化怎么他还是对着呢 我们第三点规定的是没有默认构造的自定义类型我们必须初始化。 但是类B它有默认构造函数上面将默认构造函数写了无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数都可以认为是默认构造函数我们对于自定义类型会调用他的默认构造函数将其初始化这是默认构造函数对于自定义类型的特点。 所以这是没有问题的B类的对象_bb调用自己的默认构造函数初始化了。 再看下面例子判断是否正确。 class B { public:B(int):b(1){} private:int b; };class A { public:A():_x(1),_a2(1)//引用的初始化,ret(_a1) //表示ret是_a1的引用。{_a1;_a2;} private://用缺省参数int _a1 1;int _a2 2;const int _x;int ret;B _bb; };int main() {A a;//对象整体的定义return 0; } 先说答案上面的代码是错的 对于自定义类型B,他没有默认构造函数但有构造函数。 因为它既不是全缺省也不是无参构造函数并且他写了系统也不会自动生成默认构造函数。 既然它没有默认构造函数那么他就不会自动调用默认构造函数也就不会初始化。 所以我们在初始化列表中我们必须将无默认构造函数的自定义类型初始化。 初始化过程看下面代码 class B { public:B(int):b(1){} private:int b; };class A { public:A():_bb(2) //可以随意给值有了参数就可以调用它的构造函数。{} private:B _bb; };int main() {A a;//对象整体的定义return 0; }成员变量在类中声明次序就是其在初始化列表中的初始化顺序与其在初始化列表中的先后 次序无关 利用这个知识点做下面这个题 class A { public:A(int a):_a1(a) ,_a2(_a1) {}void Print() {cout_a1 _a2endl; } private: int _a2; int _a1; }; int main() { A aa(1); aa.Print(); }答案a1 1和a2 随机值我们在类的初始化的时候传的是1调用它的构造函数声明中我们先声明了a2所以在初始化列表中我们先定义a2也就是_a2(_a1)但是这是a1并没有初始化所以a2是随机值。 然后在定义a1将a1初始化为1。 Ⅲ、explicit关键字 我们通过代码来讲问题这些都是为了引出explicit关键字大家先将下面的都行理解了。 class A { public:A(int a):_a1(a){} private:int _a2;int _a1; };int main() {//构造函数A aa1(1);//隐式类型转换A aa 1; }两个操作分别发生了什么第一个操作 A aa1(1);这个都不用想肯定是构造函数。 但是第二个操作有点奇怪A aa 1。 其实我们可以类比一下int j 0; 和double i j; 我们利用C语言的知识发生了隐式类型转换 过程是这样的将j拷贝到一个临时变量然后在转换为double最后赋值给i。 其实第二个操作也是一样的产生一个临时变量这个临时变量的类型是A*然后发生隐式类型转换将1转换为A*对临时变量发生构造函数然后临时变量再对aa进行拷贝构造函数。 所以总结一下浅浅的提一下优化下面会详细讲 第二步操作A aa 1是构造函数拷贝构造。 但是我们的编译器会进行优化将两步操作优化为一步构造。 总结构造 拷贝构造 优化 构造 explicit作用 作用用explicit修饰构造函数将会禁止构造函数的隐式转换。 ①单参数的构造函数 class A { public://单参数的构造函数explicit A(int a):_a1(a){} private:int _a2;int _a1; };int main() {//构造函数A aa1(1);//隐式类型转换//这里会报错禁止发生类型转换A aa 1; }对于单参数的构造函数 当我们在构造函数前使用explicit关键字不能进行隐式转换 ②多参数的构造函数 class A { public:A(int a1,int a2):_a1(a1),_a2(a2){} private:int _a2;int _a1; };int main() {//多参数的构造函数//C98 对多参数的类型转换不支持//C11 开始支持的//构造函数A aa1(1, 1);//隐式类型转换A aa2 { 2, 2 }; }第一个还是我们的构造函数。 第二个是我们的隐式类型转换但是是从C11才开始支持的。 如果我们不想隐式类型转换我们就加上这个关键字explicit. explicit A(int a1,int a2):_a1(a1),_a2(a2){}Ⅳ、static成员 面试题 实现一个类计算程序中创建出了多少个类对象。 //这是我们的类计算主函数中我们一共创建了几个类。 class A { public://构造函数A(int a 0){}//拷贝构造A(const Aaa){} };void Func(A a) {} int main() {A aa1;A aa2(aa1);Func(aa1);A aa3 1;return 0; }分析:我们创造键一个类。 他只有两条路一条是走构造函数一条是走拷贝构造函数。 所以我们只要计算我们进行了几次构造函数和拷贝构造函数即可。 ①方法一:创建一个全局变量。 //全局变量 int n 0; class A { public://构造函数A(int a 0){n;}//拷贝构造A(const Aaa){n;} };void Func(A a) {} int main() {//构造函数一次A aa1;//拷贝构造一次A aa2(aa1);//进行了一次拷贝构造Func(aa1);//隐式类型转换优化为一次构造函数A aa3 1;cout n endl;return 0; }这种方式可行但是这种方法的缺陷还是太明显我们n是全局变量我们随便可以随便修改一改就变了。结果就会不对。 ②方法二static成员 什么是static成员在类中用static修饰的成员变量称之为静态成员变量 静态成员变量一定要在类外进行初始化 注意静态成员变量 ,不属于某个对象属于所有对象属于整个类. 为什们静态成员变量一定要在类外进行初始化?我们在类中初始化的必须是类的对象但是静态成员变量不属于某个对象所以必须类外初始化。 class A { public: private://不属于某个对象属于所有对象属于整个类static int count;//声明 };int A::count 0;//定义初始化有了这个知识我们还需要一个GetCount()函数获取count 具体代码如下 class A { public://构造函数A(int a 0){count;}//拷贝构造A(const Aaa){count;}int GetCount(){//在这个函数中不受访问限定符是限制//如果在外面直接调用就可以拿到count//只可以读不可以写return count;} private://不属于某个对象属于所有对象属于整个类static int count;//声明 };int A::count 0;//定义初始化void Func(A a) {} int main() {//构造函数一次A aa1;//拷贝构造一次A aa2(aa1);//进行了一次拷贝构造Func(aa1);//隐式类型转换优化为一次构造函数A aa3 1;A aa4[10];cout aa3.GetCount() endl;return 0; }但是有一个缺陷他在最后调用的时候只用用我们的创建的对象进行调用这样是不是感觉很难受。 如果我们没有创建对象想去调用好像没有办法 这是就有了静态成员函数。 ③方法三static成员函数 我们想要我们的成员变量必须先创建一个对象再通过Get函数得到私有 的成员变量。 我们不想要我们的通过创建一个对象来调用函数从而得到数据那我们有没有什么方法呢》其实我们的静态成员函数就可以很好的帮我们解决这个问题。 static成员函数怎么用就是在成员函数前加一个static就变成了静态成员函数。 static成员函数的特点就是没有this指针我们知道了域名就可以直接调用。 可能有的人没有想明白为什们没有this指针就可以直接调用 我们知道类中的成员函数都有一个隐藏的this指针可以帮我们指向对应的成员变量。 所以我们在传参的时候我们必须传一个this指针就像 aa3.GetCount()它的this指针就是aa3对象的指针。 所以静态成员函数突破了这个限制在传参的时候不传this就可以用域名找到。 static成员函数能不能调用我们的非静态成员变量答案肯定是不可以但是我们要明白为什们不可以。 我们用我们的成员变量都是用this指针进行调用但是我们上面说了静态成员函数没有this肯定就不可以调用呀其实我们多想一想就可以明白。 什么时候用static成员函数当我们需要访问静态成员变量并且可以用域名直接找到静态成员函数。 class A { public://构造函数A(int a 0){count;}//拷贝构造A(const Aaa){count;}//静态成员函数static int GetCount(){return count;} private://不属于某个对象属于所有对象属于整个类static int count;//声明 };int A::count 0;//定义初始化void Func(A a) {} int main() {//构造函数一次A aa1;//拷贝构造一次A aa2(aa1);//进行了一次拷贝构造Func(aa1);//隐式类型转换优化为一次构造函数A aa3 1;A aa4[10];cout A::GetCount() endl;return 0; }Ⅴ、 匿名对象 匿名对象的样式类名直接加括号。例如上面类的匿名对象是A()。 匿名对象的特征他没有名字并且声明周期只在它这一行一旦跳过这一行直接析构。 举个例子 ④方法四用匿名对象调用然后-1。 class A { public://构造函数A(int a 0){count;}//拷贝构造A(const Aaa){count;}//int GetCount(){return count;} private://不属于某个对象属于所有对象属于整个类static int count;//声明 };int A::count 0;//定义初始化void Func() {A aa4[10]; } int main() {Func();//这个就是匿名对象。cout A().GetCount() -1 endl;return 0; }Ⅵ、友元 友元友元提供了一种突破封装的方式有时提供了便利。 但是友元会增加耦合度破坏了封装所以 友元不宜多用。 友元分为友元函数和友元类 ① 友元函数 我们上面的流插入和流插入基本将友元函数讲的差不多。能不用就不用。 友元函数的特征 友元函数可访问类的私有和保护成员但不是类的成员函数友元函数不能用const修饰(const修饰的this,只能修饰非静态的成员函数只有他们有this)友元函数可以在类定义的任何地方声明不受类访问限定符限制一个函数可以是多个类的友元函数友元函数的调用与普通函数的调用原理相同 ②友元类 他是一个但单向关系你的是我的我的还是我的。 对于下面这个Date是Time的友元所以Date可以访问Time但是Time不能访问Date class Time {friend class Date; // 声明日期类为时间类的友元类则在日期类中就直接访问Time类 中的私有成员变量 public:Time(int hour 0, int minute 0, int second 0): _hour(hour), _minute(minute), _second(second){} private:int _hour;int _minute;int _second; }; class Date { public:Date(int year 1900, int month 1, int day 1): _year(year), _month(month), _day(day){}void SetTimeOfDate(int hour, int minute, int second){// 直接访问时间类私有的成员变量 _t._hour hour;_t._minute minute;_t._second second;} private:int _year;int _month;int _day;Time _t; };友元类的特征 友元关系是单向的不具有交换性。友元关系不能传递如果C是B的友元 B是A的友元则不能说明C时A的友元。友元关系不能继承在继承位置再给大家详细介绍。 Ⅶ、内部类 很简单就是在类的里面再定义一个类。 class A { public://B就是内部类class B{private:int b 2;}; private:int h 1; };拥有内部类的类的大小 计算上面A类的大小。 答案是4. 所以我们可以初步判断一下A类的大小只有他自己并没有计算B。 内部类其实和将类定义到全局没有区别。他是独立的 用上面举个例子类B是类A的内部类但是类B是独立的只是受域的限制必须再A域中才能找到B。 还有一个特点B天生就是A的友元B可以访问A但是A不能访问B。 Ⅷ、编译器的优化 优化的过程都在代码中的注释大家好好研究 class A { public://构造函数A(int a 0):_a(a){cout A(int a) endl;}//拷贝构造A(const A aa):_a(aa._a){cout A(const A aa) endl;}//运算符重载A operator(const A aa){cout A operator(const A aa) endl;if (this ! aa){_a aa._a;}return *this;}//析构函数~A(){cout ~A() endl;} private:int _a; };void fun(A aa) {} void func(const A aa) {} int main() {//隐式类型转化//构造拷贝构造优化为构造A aa1 1;//传值传参//拷贝构造,没有优化fun(aa1);//构造拷贝构造优化为构造fun(2);//构造拷贝构造优化为构造fun(A(3));//引用传参//没有优化啥都不调用func(aa1);//无优化隐式转换为直接引用func(2);//无优化 就是构造完直接引用func(A(3));return 0; }再来一种形式 A function() {//进行构造函数A aa;//在返回的时候进行拷贝构造return aa; } int main() {function();//function()函数中一个构造一个拷贝构造//然后再一个拷贝构造//被优化为一个构造拷贝构造。A aa1 function(); }再来一种形式 A function2() {//直接返回一个匿名对象。//进行一个构造函数拷贝构造//被优化为一个构造return A(); } int main() {function2();//上面说函数被优化为一个构造//在下面那就进行一个构造拷贝构造//被优化为一个构造A aa1 function2(); }优化总结 对象返回总结 接受返回值对象尽量拷贝构造方式接收不要赋值接收 函数中返回对象时尽量返回匿名对象 函数传参优化 尽量使用const 传参。也就是引用传参。 知识总结加训练 写一个日期类 还是我们要创建一个项目。 还是分成三个文件 Date.h将我们的声明以及头文件包含。 Date.cpp函数的实现。 test.cpp测试我们这个类 Date.h #pragma once#includeiostream #includeassert.h using namespace std; class Date {friend ostream operator(ostream out, const Date d);friend istream operator(istream in, Date d); public:Date(int year 1900, int month 1, int day 1);int GetMonthDay(int year, int month);void Print();bool operator(const Date d);bool operator (const Date d);bool operator (const Date d);bool operator (const Date d);bool operator (const Date d);bool operator ! (const Date d);Date operator(const Date d);Date operator(int day);Date operator(int day);Date operator-(int day);//d1-100Date operator-(int day);//d1-d2int operator-(const Date d);//前置加加 d1Date operator(); //编译器自动匹配//后置加加 d1//参数是为了构成函数重载//规定必须是整型可以不加参数//仅仅是为了占位跟前置重载区分Date operator(int); //编译器自动匹配//前置减减 --d1 -d1.operator--()Date operator--();//后置减减 d1-- -d1.operator--(1)Date operator--(int); //编译器自动匹配输出流//void operator(ostream out);private:int _year;int _month;int _day; };inline ostream operator(ostream out, const Date d) {out d._year 年 d._month 月 d._day 日 endl;return out; }inline istream operator(istream in, Date d) {in d._year d._month d._day;return in; }Date.cpp #define _CRT_SECURE_NO_WARNINGS 1 #includeDate.h int Date::GetMonthDay(int year, int month) {assert(month 0 month 13);int monthArray[13] { 0,31,28,31,30,31,30,31,31,30,31,30,31 };if (month 2 ((year % 4 0) year % 100 ! 0) || year % 400 0){return 29;}else{return monthArray[month];} }Date::Date(int year, int month , int day ) {if (month 0 month 13 day0 day GetMonthDay(year, month)){_year year;_month month;_day day;}else {cout 日期非法 endl;}}void Date::Print() {cout _year / _month / _day / endl; } bool Date::operator(const Date d) {return _year d._year _month d._month _day d._day; } bool Date::operator (const Date d) {if (_year d._year){return true;}else if (_year d._year _month d._month){return true;}else if (_year d._year _month d._month _day d._day){return true;}else{return false;} } bool Date::operator (const Date d) {return *this d || *this d; }bool Date::operator (const Date d) {return !(*this d); }bool Date::operator (const Date d) {return !(*this d); }bool Date::operator ! (const Date d) {return !(*this d); }Date Date::operator(const Date d) {if (this ! d){_year d._year;_month d._month;_day d._day;}return *this;}//返回值是为了连续赋值。 Date Date::operator(int day) {if (day 0){*this - -day;return *this;}_day day;while (_day GetMonthDay(_year, _month)){_day - GetMonthDay(_year, _month);_month;if (_month 13){_year;_month 1;}}return *this; }//Date Date::operator(int day) //{ // Date tmp(*this); // tmp._day day; // while (tmp._day GetMonthDay(tmp._year, tmp._month)) // { // tmp._day - GetMonthDay(tmp._year, tmp._month); // tmp._month; // if (tmp._month 13) // { // tmp._year; // tmp._month 1; // } // } // return tmp; //}Date Date::operator(int day) {Date tmp(*this);tmp day;return tmp; }//前置加加 d1 Date Date::operator() {*this 1;return *this; }//后置加加 d1 Date Date::operator(int) {Date tmp(*this);*this 1;return tmp; }Date Date:: operator-(int day) {if (day 0){*this -day;return *this;}_day - day;while (_day 0){--_month;if (_month 0){--_year;_month 12;}_day GetMonthDay(_year, _month);}return *this; }//d1-100 Date Date:: operator-(int day) {Date tmp(*this);tmp - day;return tmp; }//前置减减 --d1 -d1.operator--() Date Date::operator--() {*this - 1;return *this; } //后置减减 d1-- -d1.operator--(1) Date Date::operator--(int)//编译器自动匹配 {Date tmp(*this);*this - 1;return tmp; }//d1-d2 int Date::operator-(const Date d) {Date max *this;Date min d;int flag 1;if (*this d){max d;min *this;flag -1;}//int n 0;while (min ! max){min;n;}return n * flag; }输出流 //void Date::operator(ostream out) //{ // out _year 年 _month 月 _day 日 endl; //}//ostream operator(ostream out,const Date d) //{ // out d._year 年 d._month 月 d._day 日 endl; // // return out; //} // // //istream operator(istream in, Date d) //{ // // in d._year d._month d._day; // return in; //}总结 我这篇博客几乎包括了类和对象的所有东西如果你真的看懂了说明你入门了加油
http://www.w-s-a.com/news/707709/

相关文章:

  • 网站推广seo是什么wordpress 去除顶部
  • 建筑学不会画画影响大吗电子商务沙盘seo关键词
  • 重庆网站建设找承越上海建设工程招投标网
  • 网站建设四个步骤下单的网站建设教程
  • 网站建设合同的验收表响应式网站建设哪家好
  • 手机网站建设视频长沙百家号seo
  • 网站未备案怎么访问网站开发前端需要学什么
  • 正黄集团博弘建设官方网站wordpress设置固定链接和伪静态
  • wordpress 建网站视频如何实现网站生成网页
  • 杭州品牌网站建设推广个人的网站建设目标
  • 济南有哪些网站是做家具团购的贸易公司自建免费网站
  • wap网站psd成立公司在什么网站
  • 网站建设婚恋交友聊城网站建设费用
  • 沈阳网站建设联系方式尉氏县金星网架公司
  • 医院网站建设实施方案基础微网站开发信息
  • 网站建设开发服务费记账百度指数搜索
  • 网站建设备案流程windows优化大师有必要安装吗
  • 怎么网站定制自己做网站卖视频
  • 网站开发二线城市网站制作过程中碰到的问题
  • 最好网站建设公司制作平台小程序开发教程资料
  • 陕西省高速建设集团公司网站国内做会展比较好的公司
  • 建设学校网站的原因网页设计实训报告1500
  • 网站建设客户来源江门网站设计华企立方
  • 自己如何做棋牌网站宁波网络推广优化方案
  • 深圳招聘网站推荐seo网站推广方案
  • 彩票网站开发 合法学术会议网站建设
  • 商务网站建设论文答辩pptseo技术博客
  • 怎样才能有自己的网站桂林搭建公司
  • 哪个网站做视频赚钱万科
  • 莆系医疗网站建设wp如何做网站地图