记录一下c++的学习过程,笔记如有错误,欢迎指出!!!
文章目录
- 对象的构造和析构
- 构造函数
- 特性
- 构造函数的调用
- 构造函数的调用规则
- 拷贝构造函数的调用时机
- 深拷贝与浅拷贝
- 初始化列表
- 类对象做类中成员
- explicit关键字
- 析构函数
- 特性
- new和delete
- malloc的缺陷
- new和malloc的区别
- new和delete的使用
- 静态成员变量(函数)
- 静态成员变量注意事项
- 静态成员函数注意事项
- 单例模式
- ==单例模式案例-打印机==
- 面试题:在main函数执行之前执行函数
- C++对象模型初探
- 类对象的大小
- this指针
- 用途
对象的构造和析构
对象的构造函数,按照代码执行顺序执行,析构函数按照出栈顺序执行(与构造函数顺序相反)
构造函数
特性
- 没有返回值,不用写void
- 函数名与类名相同
- 可以有参数,可以发生重载
- 构造函数,由编译器自动调用,无须手动调用
按参数分类
- 无参
- 有参
按类型分类
- 普通构造函数
- 拷贝构造函数
构造函数的调用
- 括号法
- 显式法
- 隐式法
class Person
{
public:
Person()
{
cout << "Person的默认构造函数调用" << endl;
}
Person(int age)
{
m_Age = age;
cout << "Person的有参构造函数调用" << endl;
}
//拷贝构造函数, 注意形参 const Person &p, &:为了防止拷贝构造函数进入递归循环, const为了防止p被修改
//当实参传入形参时,就要执行拷贝构造函数,即使函数体没内容
Person(const Person &p)
{
cout << "Person的拷贝构造函数调用" << endl;
m_Age = p.m_Age;
}
//析构函数
~Person()
{
cout << "Person的析构函数调用" << endl;
}
int m_Age;
};
//构造函数的调用
void test01()
{
Person p;
//1、括号法
Person p1(10);
Person p2(p);
//注意事项一
//不要用括号法 调用无参构造函数 Person p3(); 编译器认为代码是函数的声明
//2、显示法
Person p3 = Person(10); //有参构造
Person p4 = Person(p3); //拷贝构造
Person(10); //匿名对象 特点: 当前行执行完后 立即释放
cout << "aaa" << endl;
//注意事项二
//不要用拷贝构造函数 初始化 匿名对象 Person(p3); 编译器认为 Person p3对象实例化 如果已经有p3 p3就重定义
//3、隐式法
Person p5 = 10; //Person p5 = Person(10);
Person p6 = p5;
}
构造函数的调用规则
- 默认情况下,编译器会给一个类至少添加三个函数, 默认构造函数(空实现) 析构函数(空实现) 拷贝构造(值拷贝)
- 如果我们自己提供了有参构造函数,编译器就不会提供默认构造函数,但是依然会提供拷贝构造函数
- 如果我们自己提供了 拷贝构造函数,编译器就不会提供其他构造函数
拷贝构造函数的调用时机
- 用已经创建好的对象来初始化新的对象 调用拷贝构造函数
- 值传递的方式 给函数参数传值 此时函数参数不是引用
- 以值方式 返回局部对象,返回值相当于一个匿名对象,给接收者进行赋值,函数结束后,临时对象会被
class Person
{
public:
Person()
{
cout << "Person的默认构造函数调用" << endl;
}
Person(int age)
{
m_Age = age;
cout << "Person的有参构造函数调用" << endl;
}
//拷贝构造函数
Person(const Person &p)
{
cout << "Person的拷贝构造函数调用" << endl;
m_Age = p.m_Age;
}
//析构函数
~Person()
{
cout << "Person的析构函数调用" << endl;
}
int m_Age;
};
//1、用已经创建好的对象来初始化新的对象
void test01()
{
Person p1(18);
Person p2 = Person(p1);
cout << "p2的年龄:" << p2.m_Age<< endl;
}
//2、值传递的方式 给函数参数传值
void doWork(Person p)
{
}
void test02()
{
Person p1(100);
doWork(p1);
}
//3、以值方式 返回局部对象
Person doWork2()
{
Person p;
return p;
}
void test03()
{
Person p = doWork2();
}
深拷贝与浅拷贝
浅拷贝只拷贝变量中存放的内容,例如只拷贝地址的值,而不拷贝地址指向的内容,当有多个对象指向同一块内容时,释放对象时,就会多次释放相同地址指向的内容,导致报错。 因此,需要深拷贝解决,深拷贝不是简单的值的拷贝,而是另外开辟一块空间,拷贝地址指向的内容
class Person
{
public:
Person( char * name ,int age )
{
m_Name = (char *) malloc(strlen(name) + 1);
strcpy(m_Name, name);
m_Age = age;
}
Person(const Person&p)
{
m_Name =(char*)malloc(strlen(p.m_Name) + 1);
strcpy(m_Name, p.m_Name);
m_Age = p.m_Age;
}
~Person()
{
if (m_Name != NULL)
{
cout << "Person析构调用" << endl;
free(m_Name);
m_Name = NULL;
}
}
char * m_Name; //姓名
int m_Age; //年龄
};
void test01()
{
Person p("德玛西亚", 18);
cout << "姓名: " << p.m_Name << " 年龄: " << p.m_Age << endl;
Person p2(p);
cout << "姓名: " << p2.m_Name << " 年龄: " << p2.m_Age << endl;
}
初始化列表
可以用初始化列表语法,给类中的属性进行初始化,形式如下
//构造函数名称后 : 属性(值), 属性(值)...
Person(int a, int b, int c) : m_A(a), m_B(b), m_C(c)
{
}
类对象做类中成员
class Phone
{
public:
Phone(string name) :m_Pname(name)
{
cout << "Phone的有参构造函数调用" << endl;
}
Phone(const Phone& phone)
{
m_Pname = phone.m_Pname;
cout << "Phone的拷贝构造函数调用" << endl;
}
~Phone()
{
cout << "Phone的析构函数调用" << endl;
}
string m_Pname;
};
class Game
{
public:
Game(string name): m_Gname(name)
{
cout << "Game的有参构造函数调用" << endl;
}
Game(const Game& game)
{
m_Gname = game.m_Gname;
cout << "Game的拷贝构造函数调用" << endl;
}
~Game()
{
cout << "Game的析构函数调用" << endl;
}
string m_Gname;
};
class Person
{
public:
Person(string p_name, string g_name, string name) :m_phone(p_name), m_game(g_name), m_name(name)
{
cout << "Person的有参构造函数调用" << endl;
}
Person(Phone phone, Game game, string name) :m_phone(phone), m_game(game), m_name(name)
{
cout << "Person的有参构造函数调用" << endl;
}
~Person()
{
cout << "Person的析构函数调用" << endl;
}
string m_name;
Phone m_phone;
Game m_game;
};
void test01()
{
Phone phone("vivo");
Game game("王者荣耀");
Person person(phone, game, "jiege"); //先将实参传入形参,依据调用惯例,从右向左入栈,依次调用拷贝构造函数
//随后,在函数中,再次依据参数列表调用拷贝构造函数
//Person person("vivo", "王者荣耀", "jiege");
explicit关键字
explicit用途: 防止利用隐式类型转换方式来构造对象
class MyString
{
public:
MyString(char * str)
{
}
//explicit用途: 防止利用隐式类型转换方式来构造对象
explicit MyString(int len)
{
}
};
void test01()
{
MyString str1(10);
MyString str2 = MyString(100);
//MyString str3 = 10; // "10"
}
析构函数
如果有属性分配在堆区,此时需要自己写析构函数手动释放
特性
- 没有返回值,不用写void
- 函数名与类名相同,函数名前加~
- 不可以由参数,不可以发生重载
- 析构函数,也是由编译器自动调用一次,无须手动调用
new和delete
malloc的缺陷
- 程序员必须确定对象的长度。
- malloc返回一个void指针,c++不允许将void赋值给其他任何指针,必须强转。
- malloc可能申请内存失败,所以必须判断返回值来确保内存分配成功。
- 用户在使用对象之前必须记住对他初始化,构造函数不能显示调用初始化(构造函数是由编译器调用),用户有可能忘记调用初始化函数。
new和malloc的区别
- malloc 和 free 属于 库函数 new 和delete属于 运算符
- malloc不会调用构造函数 new会调用构造函数
- malloc返回void* C++下要强转 new 返回创建的对象的指针
new和delete的使用
- 堆区开辟数组,一定会调用默认构造函数
- 释放数组时候 需要加[]
- 栈上开辟数组,可以没有默认构造
- 不建议用void *接收new开辟的空间,因为无法用delete释放,非要这样的话,需要用强制转换
- new可以在构造函数为私有的情况下,创建对象,返回对象指针
class Person
{
public:
Person()
{
cout << "Person构造函数调用" << endl;
}
Person(int a)
{
cout << "Person有参构造调用" << endl;
}
~Person()
{
cout << "Person析构函数调用" << endl;
}
};
void test01()
{
Person * p = new Person; //调用默认构造函数,new Person后也可以调用 有参构造函数,或者拷贝构造函数
delete p; //调用析构函数
}
//利用new开辟数组
void test03()
{
int * pInt = new int[10];
double * pD = new double[10];
//堆区开辟数组,一定会调用默认构造函数
Person * pPerson = new Person[10]; //调用10次默认构造函数
//释放数组时候 需要加[]
delete [] pPerson; // 调用10次析构函数
Person pArray[10] = { Person(10), Person(20), Person(20) }; //调用三个有参构造函数,7个默认构造函数
}
静态成员变量(函数)
静态成员变量注意事项
- 静态成员变量 :编译阶段就分配了内存,即采用单例模式时,在静态变量定义,静态变量时就调用默认构造函数,此时构造函数比main函数早执行
- 类内声明 、类外初始化,不通过构造函数初始化
- 静态成员变量 所有对象都共享同一份数据
- 可以通过对象或类名访问,私有静态成员变量外部访问不到
静态成员函数注意事项
- 所有对象都共享同一个func函数
- 静态成员函数 不能访问非静态成员变量
- 静态成员函数 能访问静态成员变量
- 可以通过对象或类名访问,私有静态成员函数外部访问不到
class Person
{
public:
static int m_A;
static void func()
{
//m_C = 100; //静态成员函数 不能访问非静态成员变量
m_A = 100; //静态成员函数 能访问静态成员变量
cout << "func调用" << endl;
}
int m_C;
private:
static int m_B; //私有静态成员变量
static void func2()
{
}
};
int Person::m_A = 0;
int Person::m_B = 0;
void test01()
{
//1、通过对象进行访问
Person p1;
cout << p1.m_A << endl;
Person p2;
p2.m_A = 100;
cout << p1.m_A << endl;
//2、通过类名进行访问
cout << Person::m_A << endl;
//静态成员变量 也是有访问权限的,私有权限类外访问不到
//cout << Person::m_B << endl;
}
void test02()
{
//通过对象
Person p1;
p1.func();
//通过类名
Person::func();
//Person::func2(); 静态成员函数也是有访问权限的
}
单例模式
一个类只能实例化一个对象
class ChairMan
{
public:
static ChairMan * getInstacne()
{
return singleMan;
}
private:
//将构造函数私有化,不可以创建多个对象
ChairMan(){};
ChairMan(const ChairMan &){};
private:
//将主席指针 私有化,对外提供只读接口
static ChairMan * singleMan; //类内声明 类外初始化
};
ChairMan * ChairMan::singleMan = new ChairMan;
void test01()
{
//需要屏蔽拷贝构造函数和默认构造函数
/*ChairMan c1;
ChairMan c2;
ChairMan * c3 = new ChairMan;*/
//ChairMan * c3 = new ChairMan(*c1);
//设为只读,不能修改
ChairMan * c1 = ChairMan::getInstacne();
ChairMan * c2 = ChairMan::getInstacne();
if (c1 == c2)
{
cout << "c1 = c2" << endl;
}
else
{
cout << "c1 != c2" << endl;
}
}
单例模式案例-打印机
面试题:在main函数执行之前执行函数
针对此案例:
- 由于静态成员的定义在编译时执行,所有new后面调用的此构造函数在main函数之前执行,只执行一次
- 只有一个实例,只进行一次构造函数,即使没有声名m_count为static, m_count也只初始化一次
class Printer
{
public:
static Printer * getInstance()
{
return printer;
}
void printText(string text)
{
m_Count++;
cout << text << endl;
}
int m_Count;
private:
Printer()
{
m_Count = 0;
cout << "打印机构造调用" << endl; // 由于静态成员的定义在编译时执行,所有此构造函数在main函数之前执行,只执行一次
};
Printer(const Printer & p){};
static Printer * printer;
};
Printer * Printer::printer = new Printer;
void test01()
{
//只有一个实例,只进行一次构造函数,即使没有声名m_count为static, m_count也只初始化一次
Printer * p1 = Printer::getInstance();
p1->printText("入职证明");
p1->printText("离职证明");
p1->printText("加薪申请");
p1->printText("旅游申请");
cout << "打印机使用次数: " << p1->m_Count << endl;
Printer * p2 = Printer::getInstance();
p2->printText("调休申请");
cout << "打印机使用次数: " << p1->m_Count << endl;
}
int main(){
cout << "main函数调用" << endl;
test01();
system("pause");
return EXIT_SUCCESS;
}
C++对象模型初探
类对象的大小
#pragma pack(show) //以警告的形式显示对齐模数
#pragma pack(n) //设置对齐模数为n
只有非静态成员变量属于类对象上, 成员函数,静态成员函数,静态成员变量占用的内存都不在类对象上,即sizeof(类对象)的结果是类中非静态成员变量占用的字节数(经过内存对齐后的)
#pragma pack(show)
class Person
{
public:
int m_A; //只有非静态成员变量 属于类对象上(即sizeof时占内存) 4个字节
void func( ) //成员函数 并不属于类对象上(即sizeof时不占内存),方法与属性分开存储
{
}
static int m_B; //静态成员变量 不属于类对象上(即sizeof时不占内存)
static void func2()//静态成员函数 不属于类对象上(即sizeof时不占内存)
{
}
double m_C; //只有非静态成员变量 属于类对象上(即sizeof时占内存) 8个字节
};
int Person::m_B = 0;
void test01()
{
//空类的实例的sizeof结果是1 原因 每个对象都应该在内存上有独一无二的地址,因此给空对象分配1个字节空间
Person p1;
// 空对象 大小 1
cout << "sizeof = " << sizeof(p1) << endl; //最终结果:一共12字节,但由于存在内存对齐,占16字节(假设对齐模数是8)
}
this指针
用途
- 解决名称冲突
- this指针 隐式加在每个成员函数中
- this指针 指向 被调用的成员函数 所属的对象
- 函数的返回值是引用时,返回的是经过函数处理后的原对象,返回值是类型时,返回的是经过函数处理后的对向的拷贝,与原对象不是一个东西,参考下面的链式编程
class Person
{
public:
Person(int age)
{
//用途1 :解决名称冲突
this->age = age;
}
//this指针 隐式加在每个成员函数中
bool compareAge(Person &p)
{
if (this->age == p.age)
{
return true;
}
return false;
}
Person& personAddPerson(Person &p)
{
this->age += p.age;
return *this; //*this 就是本体
}
int age;
};
void test01()
{
//this指针 指向 被调用的成员函数 所属的对象
Person p1(10);
cout << "p1的年龄为: " << p1.age << endl;
Person p2(10);
bool ret = p1.compareAge(p2);
if (ret)
{
cout << "p1与p2年龄相等" << endl;
}
p1.personAddPerson(p2).personAddPerson(p2).personAddPerson(p2); //链式编程
cout << "p1的年龄为: " << p1.age << endl;
}
#pragma pack(show)
class Person
{
public:
int m_A; //只有非静态成员变量 属于类对象上(即sizeof时占内存) 4个字节
void func( ) //成员函数 并不属于类对象上(即sizeof时不占内存),方法与属性分开存储
{
}
static int m_B; //静态成员变量 不属于类对象上(即sizeof时不占内存)
static void func2()//静态成员函数 不属于类对象上(即sizeof时不占内存)
{
}
double m_C; //只有非静态成员变量 属于类对象上(即sizeof时占内存) 8个字节
};
int Person::m_B = 0;
void test01()
{
//空类的实例的sizeof结果是1 原因 每个对象都应该在内存上有独一无二的地址,因此给空对象分配1个字节空间
Person p1;
// 空对象 大小 1
cout << "sizeof = " << sizeof(p1) << endl; //最终结果:一共12字节,但由于存在内存对齐,占16字节(假设对齐模数是8)
}