构造函数
构造函数
初始化:被初始化的对象正在创建
赋值:被赋值的对象已经存在
构造函数为了提供初始化用的,
构造函数有:默认构造函数,拷贝构造函数
// Test2(int a=0, int b=0): m_a(a),m_b(b) {} 推荐这样写
#include <iostream> using namespace std; // 同一个项目最好不要有相同的类名(防止串包) // 默认构造函数:可以写成有参数的并且,形参写上初始值 class Test2 { public: Test2() { cout << "无参构造函数" << endl; } Test2(int a) { m_a = a; } Test2(int a, int b) // 调用有参构造函数 3种方法 { m_a = a; m_b = b; cout << "有参构造函数" << endl; } // 赋值构造函数(拷贝构造函数) Test2(const Test2 & t2) { cout << "拷贝构造函数" << endl; } ~Test2() { } void printT() { cout << "普通成员函数" << endl; } private: int m_a; int m_b; }; // 调用有参构造函数 三种方法 第一种比较常用 int main() { // Test2 t1; // 调用无参构造函数 // Test2 t1(); // 调用有参构造函数(编译能通过,使用的时候回报错) // 1 括号法 Test2 t1(1, 2); // c++编译器帮我们调用构造函数(t1和t2都是) t1.printT(); // 2 = 号法 // t2不建议使用 Test2 t2 = (1, 2, 3, 4); // 报错因为没有一个参数的构造函数,如果有的话就会调用一个的把4用来初始化 Test2 t3 = 5; // 调用一个参数的构造函数 // 3直接调用构造函数 手动调用 // 调用一次后面的然后转成t4 Test2 t4 = Test2(1, 2); // 匿名对象(匿名对象的去和留)抛砖。t4对象初始化 t1 = t4; // 把t4拷贝给t1 拷贝构造函数 // 对象的初始化和对象的赋值是两个概念 return 0; }
拷贝构造函数调用的时机
拷贝构造函数调用时机
将一个对象作为实参传递给一个非引用类型的形参
从一个返回类型为非引用的函数返回一个对象(匿名对象)
用一个类型去初始化另外一个类型 T t1 = t2
用花括号列表初始化一个数组中的元素或者一个聚合类(C++primer 266)
struct data {
int a;
string s;
};
// data test; test.a = 1; test.s = "abcd";
data test = {1, "abcd"};
注意:
//匿名对象的去和留,关键看,返回时如何接
//若返回的匿名对象,赋值给另外一个同类型的对象,那么匿名对象会被析构
//若返回的匿名对象,来初始化另外一个同类型的对象,那么匿名对象会直接转成新的对象
调用测试
用一个类型去初始化另外一个类型
Test4 t2 = t1; // 用t1来初始化t2 拷贝构造函数
Test4 t3(t1); // 用一个对象初始化另一个对象
#include <iostream> using namespace std; // 同一个项目最好不要有相同的类名(防止串包) class Test4 { public: Test4(int a, int b) // 调用有参构造函数 { m_a = a; m_b = b; cout << "有参构造函数" << endl; } // 赋值构造函数(拷贝构造函数) Test4(const Test4 & t) { cout << "拷贝构造函数" << endl; m_a = t.m_a + 100; m_b = t.m_b + 100; } void printT() { cout << "普通成员函数" << endl; cout << "m_a = " << m_a << " m_b = " << m_b << endl; } private: int m_a; int m_b; }; // 构造函数是用来初始化的(拷贝..,构造函数) // 1 拷贝构造函数;用一个对象去初始化另一个对象 int main() { Test4 t0(1,2); Test4 t1(1,2); // *** 赋值=操作 不会调用构造函数 **** // operator= 运算符重载 t0 = t1; // 用t1给t0 赋值 和 初始化 是两个概念 // 第一种调用时机 Test4 t2 = t1; // 用t1来初始化t2 拷贝构造函数 t2.printT(); // 第二种调用时机 Test4 t3(t1); // 用一个对象初始化另一个对象 t2.printT(); return 0; }
将一个对象作为实参传递给一个非引用类型的形参
f(类的对象作为参数); // b实参去初始化形参 会调用拷贝构造函数,
void f(类名 非饮用形参)
#include <iostream> using namespace std; class Location { public: Location(int _x, int _y) { x = _x; y = _y; cout << "\t构造函数" << endl; } // 拷贝构造函数 用一个对象初始化另一个对象 Location(const Location &obj) { x = obj.x; y = obj.y; cout << "\t拷贝构造函数" << endl; } int getX() {return x;} int getY() {return y;} private: int x; int y; }; // 业务函数 void f(Location p) { cout << p.getX() << endl; } void play() { Location a(1, 2); Location b = a; cout << "\tb对象已经初始化完毕" << endl; f(b); // b实参去初始化形参 会调用拷贝构造函数 } int main() { play(); return 0; }
从一个返回类型为非引用的函数返回一个对象(匿名对象)
#include <iostream> using namespace std; class Location { public: Location() = default; Location(int _x, int _y) { x = _x; y = _y; cout << "\t构造函数" << endl; } // 拷贝构造函数 用一个对象初始化另一个对象 Location(const Location &obj) { x = obj.x; y = obj.y; cout << "\t拷贝构造函数" << endl; } ~Location() { cout << "\t析构函数" << endl; } int getX() {return x;} int getY() {return y;} private: int x; int y; }; // 结论1: 函数的返回值是一个元素(复杂类型) 返回的是一个新的匿名对象 // (所以会调用匿名对象类的拷贝构造函数) // 结论2: 有关匿名对象的去和留 // 如果用匿名对象 初始化 另外一个同类型的对象,匿名对象转成有名对象(objplay3函数) // 如果匿名对象 赋值(=) 给另外一个同类型的对象, 匿名对象马上被析构(objplay4) // 特别注意这里 在不同的编译器上不同,A应该会在返回时(就算不接收)也会调用拷贝构造函数 // 匿名对象的去和留 // 你这么写代码,c++编译器的大牛们: // 我就给你返回一个新对象(没有名字) Location g() // 最好在objplay4时使用此函数 { Location A(1, 2); return A; // 这里返回时会调用拷贝构造函数 } void objplay2() { g(); } void objplay3() { // 用匿名对象初始化m,c++编译器 直接把匿名对象转成m // 从无名,转为有名 Location m = g(); // 这里不会再次调用拷贝构造函数了(匿名对象时已经调用了) cout << "\t匿名对象,被扶正,不会被析构掉"; cout << m.getY() << endl; } void objplay4() { // 用匿名对象赋值给m2后,匿名对象被析构 Location m2(1, 2); m2 = g(); cout << "\t因为用匿名对象=m2,匿名对象被析构"; cout << m2.getY() << endl; } int main() { // objplay2(); // objplay3(); // objplay4(); return 0; }
https://www.cnblogs.com/xiaokang01/p/9160475.html#_label1_4
看函数返回值的匿名对象
深浅拷贝-->后面有个static
当在类中出现指针时就要注意了,出现指针(内存重新分配)时不能使用编译器的构造函数和赋值函数了还有析构函数,要自己写
深拷贝和浅拷贝可以简单理解为:如果一个类拥有资源,当这个类的对象发生复制过程的时候,资源重新分配,这个过程就是深拷贝,反之,没有重新分配资源,就是浅拷贝。
new和delete
强化训练
某商店经销一种货物。货物购进和卖出时以箱为单位,各箱的重量不一样,
因此,商店需要记录目前库存的总重量。
现在用C++模拟商店货物购进和卖出的情况。
#include "iostream" using namespace std; class Goods { public : Goods(int w) { weight = w; total_weight += w; } ~ Goods() { total_weight -= weight; } int Weight() { return weight; }; static int TotalWeight() { return total_weight; } Goods *next; private : int weight; static int total_weight; }; int Goods::total_weight = 0; //r尾部指针 void purchase(Goods *&f, Goods *&r, int w) { Goods *p = new Goods(w); p->next = NULL; if (f == NULL) f = r = p; else { r->next = p; r = r->next; } //尾部指针下移或新结点变成尾部结点 } void sale(Goods *&f, Goods *&r) { if (f == NULL) { cout << "No any goods!\n"; return; } Goods *q = f; f = f->next; delete q; cout << "saled.\n"; } int main() { Goods *front = NULL, *rear = NULL; int w; int choice; do { cout << "Please choice:\n"; cout << "Key in 1 is purchase,\nKey in 2 is sale,\nKey in 0 is over.\n"; cin >> choice; switch (choice) // 操作选择 { case 1 : // 键入1,购进1箱货物 { cout << "Input weight: "; cin >> w; purchase(front, rear, w); // 从表尾插入1个结点 break; } case 2 : // 键入2,售出1箱货物 { sale(front, rear); break; } // 从表头删除1个结点 case 0 : break; // 键入0,结束 } cout << "Now total weight is:" << Goods::TotalWeight() << endl; } while (choice); return 0; }
面向对象模型初探
C++编译器是如何管理类、对象、类和对象之间的关系
:底层通过指针,看传入的指针,然后进行区分
C++类对象中的成员变量和成员函数是分开存储的
成员变量:
普通成员变量:存储于对象中,与struct变量有相同的内存布局和字节对齐方式
静态成员变量:存储于全局数据区中
成员函数:存储于代码段中。
问题出来了:很多对象共用一块代码?代码是如何区分具体对象的那?
#include <iostream> using namespace std; // 注意上一个视频的 字节占用数 class Test { public: Test(int _a, int _b)// ==> Test(Test *this, int a, int b) { this->a = _a; this->b = _b; } void print17() { cout << "a:" << a << endl; cout << "b:" << this->b << endl; } // 1 const写在什么地方 没有关系 // 2 const修饰的是谁 // 2-3const 修饰的是this指针所指向的内存空间,修饰的是this指针 // void const OpVar(int a, int b)这两个一样 不过一般是放在后面 void OpVar(int a, int b) const // ==>void OpVar(const Test *this, int a, int b) // 更精确点是void OpVar(const Test* cosnt this, int a, int b) { a = 100; // this->a = 100; // 报错 // this->b = 100; cout << "b:" << this->b << endl; } // void OpVar(int a, int b) ==>void OpVar(Test* cosnt this, int a, int b) private: int a; int b; }; int main() { Test t1(1, 2); t1.print17(); // ==> printT(&t1); return 0; }
#include <iostream> using namespace std; class Test18 { public: int a; int b; Test18(int a=0, int b=0) { this->a = a; this->b = b; } public: void print18() { cout << "a:" << a << "\tb:" << b << endl; } // 成员函数法 // t3 = t1.TestAdd2(t2); Test18 TestAdd2(Test18 &t2) { // 产生一个匿名对象 Test18 temp(this->a + t2.a, this->b + t2.b); return temp; } //t1.TestAdd3(t2); // 这里返回的是t1 Test18& TestAdd3(Test18 &t2) { this->a += t2.a; this->b += t2.b; return *this; // this 就是t1的地址,*t1又回到t1的元素 } ~Test18() { cout << "析构函数自动被调用"; cout << "a:" << a << "\tb:" << b << endl;; } private: }; // 全局函数方法 Test18 TestAdd(Test18 &t1, Test18 &t2) { Test18 temp(t1.a+t2.a, t1.b + t2.b); return temp; } int main1802() { Test18 t1(1, 2); Test18 t2(3, 4); // t1 = t1+t2; t1.TestAdd3(t2); t1.print18(); return 0; } int main1801() { Test18 t1(1, 2); Test18 t2(3, 4); Test18 t3; // t1 + t2; // 全局函数法 t3 = TestAdd(t1, t2); t3.print18(); // 成员函数法 // 先把测试案例写出来 重点是思路 { // 匿名对象的两种接法 Test18 t4 = t1.TestAdd2(t2); // 匿名对象转正 转给4 t4.print18(); Test18 t5; t5 = t1.TestAdd2(t2); // 匿名对象 复制给5 最好用这个 // t5接收后 前面需要调用析构函数,而t4不用 t5.print18(); } return 0; }
友元函数和友元类
友元函数:在成员函数的前面添加关键字friend ,破坏了 类的封装性, 但是在重载 << 时要用到
友元类:了解就行
#include <iostream> using namespace std; class A { public: friend class B; // B类是A类的好朋友,就是说在B类 可以修改A类的 私有属性,私有函数 // 1声明的位置 和public和private无关系 friend void modify(A &p, int _a); // 2 modify函数是类A的朋友 A(int a = 0, int b = 0) { this->a = a; this->b = b; } void printA() { cout << "a = " << a << "\tb = " << b << endl; } private: int a; int b; }; class B { public: void Set(int b) { Aobject.b = b; } void printB() { cout << "Aobject.b = " << Aobject.b << endl; } private: A Aobject; }; // 不加友元函数声明就会报错 void modify(A &p, int _a) { // p.a = 100; p.a = _a; } // 友元类 先了解, 友元函数 要会 // 为什么要设计友元函数 int main() { B b1; b1.Set(200); b1.printB(); return 0; } int main2101() { A a1(1, 2); a1.printA(); modify(a1, 300); a1.printA(); return 0; }