概念
- 成员变量:属性
- 成员函数:方法
类
类在编译后不占内存空间,所以在定义类时不能对成员变量进行初始化,因为没有地方存储数据
只有在创建对象后才会给成员变量分配内存,并赋值
可以将类看做一种数据类型
补充 多文件
类的内部包含成员变量和成员函数,成员变量要等到具体的对象被创建时才会被定义(分配空间),但是成员函数需要在一开始就被定义,也就是类的实现
成员函数实现可以写在类定义的内部,然后放在头文件中
成员函数的定义不再类定义内部实现的情况下,不能早头文件中定义
类定义/实例化
class 类定义后面有一个分号,要注意!!!
struct 结构体定义后面也有一个分号
类可以定义在函数外,和函数内(通常很少定义在函数内)
对象创建的两个阶段:分配内存空间,再进行初始化
初始化才是调用构造函数
对于返回值是结构体和类对象时(值传递),为了防止局部对象被销毁,也为了防止通过返回值修改原来的局部对象,编译器并不会返回这个对象,而是根据这个对象先创建一个临时对象(匿名对象)以拷贝的方式进行,然后再将这个临时对象返回
class Student
{
public:
char *name;
int age;
void say();
// 定义
// void say(){cout<<"hello"<<endl;}
};
实例化
Student stu1; // 等价于class Student stu1
// 还可以写成
Student stu1=Student();
互相赋值!!!
相同成员下的对象可以赋值
Student stu1,stu2;
stu1=stu2;
实例对象数组
对象数组,数组中每个元素都是一个类的实例对象
class ClsName
{
dataType data;
};
ClsName instanceItems[Size]; //创建数组
对象指针/地址/new
注意
clsType *obj
并不会调用构造函数,只是定义了一个指向对象的指针,没有实际开辟内存
在栈上创建实例对象
class_name *ptr=&instance
ptr指向一个class_name类型的数据(实例对象)
class Student
{
char *name;
int age;
};
int main()
{
Student stu; // 在栈上创建分配对象的内存 Student Stu=Student()
// 地址,&stu;
Student *pStu=&stu;
}
在堆上创建实例对象
class_name *ptr=new class_name
dataType *ptr=new dataType
https://blog.csdn.net/baidu_31437863/article/details/86558339
int *p=new int[10]; // 因为实际数组的类型是int[10]
delete[] p;
Student *pStu=new Student;
// Student *pStu;
// pStu=new Student;
delete pstu;
栈/堆创建对象的区别
栈上创建有名字,指针不必要
堆上创建只能通过指针创建
例子补充
String::String(const char *s)
{
len=strlen(s);
str=new char[len+1];
strcpy(str,s);
}
成员与访问
栈上访问
instance.property/method(); // 进行访问
堆上访问
instance->property/method(); // 进行访问
public 公有
在类内可访问
类外 通过实例对象访问
公有方法可以访问公有属性
公有方法可以访问私有属性
公有方法可以调用公有方法
公有方法可以调用私有方法
实例对象可以访问公有属性
实例对象可以访问公有方法
类名可以直接访问公有属性、公有访问
修改公有属性,对多个实例对象之间没有影响
private 私有
在类内可访问,可通过对象的指针进行访问
在类外 不能通过实例对象 直接 访问
私有方法可以访问公有属性
私有方法可以访问私有属性
私有方法可以调用公有方法
私有方法可以调用私有方法
实例对象不可访问私有属性,(可以访问公有属性)
实例对象不可访问私有方法,(可以访问公有方法)
类名不可以直接访问私有属性、私有方法
private/public
注意,如果不写public,也不写private,就默认为private
一个类体里,private、public可以分别出现多次
class Student
{
private:
char *m_name;
int m_age;
public:
void func(char *name);
void func2(int age);
};
void Student::func(char *name)
{
m_name=name;
}
void Student::func2(int age)
{
m_age=age;
}
int main()
{
// 实例化
Student stu;
stu.func("zhangsan"); // 这个尽量不要写,因为这是一个常量,赋给了一个变量
stu.func2(12);
return 0;
}
例子2
Base::Base(const Base &instance)
{
/*
这里虽然是私有成员,但是拷贝构造函数传入来一个实例对象的引用相当于*this
那么就相当于在类内访问私有成员,知道作用域在哪
*/
this->a=instance.a;
this->b=instance.b;
this->count++;
this->time=std::time((time_t *)NULL);
}
protected 保护
在类外不能通过对象访问,但是它在的派生类的内部可以访问
protected成员在本类中不能通过对象访问,实际上是不能在类外使用,在类内能使用
当存在继承关系时基类中的protected成员可以在派生类中使用,(这个是相对于private的继承来说的)
成员函数 – 注意事项
c++可以在类的声明中,也可以在函数定义中声明缺省参数,但不能既在类声明中又在函数定义中同时声明缺省参数。因此,将定义或声明中的任意一个缺省参数删除即可
类的实例对象的内存模型
类的声明与定义不占用内存空间
类对象占用内存空间
编译器将成员变量、成员函数分开存储:分别对每个实例对象的成员变量分配内存,但是所有对象都共享同一段函数代码
成员变量在堆、栈区份分配内存
成员函数在代码区分配内存
sizeof(class_name); // 一般只算成员变量,注意内存对齐
类进阶
构造函数
构造函数名与类名相同 没有返回值,无需显示调用
在创建对象时自动执行
构造函数必须是public属性,否则创建对象时无法调用
不允许出现void返回值,函数体不能有return
如果用户没有自己定义构造函数,编译器会自动生成一个默认的构造函数,只是这个构造函数的函数体是空,没有形参,也不执行任何操作,
Student::Student(){}
一个类必须有构造函数,要么自己定义,要么编译器自动生成,用户定义后,编译器不会再自动生成(只要用户定义了构造函数,无论是无参还是有参构造,编译器都不在自动生成默认构造函数)
构造函数定义
class Student
{
public:
Student(char *name,int age); // 声明构造函数
private:
char *m_name;
int m_age;
};
Student::Student(const char *name,int age)
{
m_name=name;
m_age=age;
}
int main()
{
// 两种初始化方式
// 栈上创建
Student stu1("张三",1);
// 还可以写成
Student stu1=Student("张三",1); // 实际上是构造函数得到变量再通过移动构造实现的
// 堆上创建
Student *stu2=new Student("lisi",2); // 这只是普通的构造函数
delete stu2;
}
默认构造函数
无参构造函数的调用
无参构造函数就是默认构造函数
class Student
{
public:
Student();
};
// 无参数构造函数
Student::Student()
{
/*code*/
}
int main()
{
// 四种初始化方式
// 栈上创建
Student stu1;
Student stu1();
Student stu1=Student();
// 堆上创建
Student *stu2=new Student();
Student *stu2=new Student;
delete stu2;
}
缺省构造函数
注意:构造函数声明中定义了缺省参数,函数定义中就不能在写默认参数了
class MyClass
{
public:
void func(int a=1);
}
// MyClass::func(int a=1){} // 这么写是错误的
MyClass::func(int a/*=1*/){} // 可以这么写
注意2:对于全缺省的构造函数,也叫做默认构造函数;无参数的默认参数也叫做默认构造函数
所以,全缺省的构造函数和无参数的构造函数不能同时被用户自定义,在进行无参实例化的时候,编译器不知道要调用哪个默认构造函数
class Student
{
public:
// 同时出现下面两个是错误的
Student();
Student(char *name="zhangsan",int age=10);
};
int main()
{
Student stu1; // 这种情况下不知道调用哪个默认构造函数
return 0;
}
构造函数重载
构造函数可以重载
就是同名函数的不同参数列表、不同参数、不同参数顺序、不同参数类型
参数名不同不可以;只有缺省或不缺省不可以
// 下面两种算一种方法,同时出现会报重定义的错误
void MyClass(int a);
void MyClass(int a=1);
构造函数初始化列表
https://blog.csdn.net/gx714433461/article/details/124285721
除了上述的初始化方法外,还可以使用初始化列表的方式对成员变量初始化
对象初始化列表要优先于当前对象的构造函数
注意:使用初始化列表,列表中的赋值语句顺序与类定义中的声明顺序无关,(所以实际编程中最好就是注意赋值顺序)
class Student
{
public:
Student(char *name,int age); // 声明构造函数
private:
char *m_name;
int m_age;
};
// 普通赋值构造函数
Student::Student(const char *name,int age)
{
m_name=name;
m_age=age;
}
// 利用初始化列表实现构造函数
Student::Student(const char *name,int age)
:m_name(name),
m_age(age) // 与上面的方法等价
{
}
int main()
{
// 两种初始化方式
// 栈上创建
Student stu1("张三",1);
// 堆上创建
Student *stu2=new Student("lisi",2);
delete stu2;
}
// 注意的例子
Student::Student(const char *name,int age)
:m_age(age),m_name(name)
{
// 依然执行的顺序是
// 所以当赋值有关联的时候,要注意顺序问题
// 因为私有变量先声明m_name,后声明m_age,所以,使用初始化列表时,也会先执行m_name(name),后执行m_age(age)
m_name=name;
m_age=age;
}
初始化列表其他注意事项
- 初始化列表只能初始化一次,多次初始化会报错
- 初始化列表与构造函数内赋初值可以混合使用,且混合使用时多次赋值不冲突
- const成员变量必须使用初始化列表进行初始化
- 引用成员变量碧玺使用初始化列表进行出初始化(因为引用变量必须在定义时初始化)
- 没有默认构造函数的自定义类型成员变量,必须在初始化列表中进行初始化
class A
{
public:
A(int x):x(x); // 没有默认构造函数
private:
int x
};
class B
{
public:
B(int val);
private:
A _a;
int &_val;
};
B::B()
: _val(val)
, _a(20)
{
}
const 成员变量的初始化
类在编译后不占内存空间,所以在定义类时不能对成员变量进行初始化,因为没有地方存储数据
只有在创建对象后才会给成员变量分配内存,并赋值
const 成员变量的初始化,必须使用初始化列表的方法
class Student
{
private:
const int m_len;
public:
Student(int len);
};
Student::Student(int len)
:m_len(len) // 或者直接写 m_len(10)
{
// 不能直接写成
// m_len=len
}
Student::Student(*args,**kwargs)
:m_len(10)
{
// 不能直接写成
// m_len=len
}
c11标准特性
从例子来看,对于隐式转换(列表初始化)来说,其实调用的是构造函数(与构造函数的初始化列表没有关系)
注意,只有单参数的可以直接使用clsType=val
,对于多参数的类,只能使用clsType={val1,val2}
#include <iostream>
#include <string>
using namespace std;
class Student
{
private:
string name;
int age;
public:
Student(string name);
Student(string name,int age);
void print();
};
Student::Student(string name)
:name(name),age(10)
{
// this->age=10;
cout<<"单参数构造函数"<<endl;
}
Student::Student(string name,int age)
:name(name),age(age)
{
// this->age=age;
// this->name=name;
cout<<"多参数构造函数"<<endl;
}
void Student::print()
{
cout<<this->name<<" "<<this->age<<endl;
}
int main()
{
Student s1={"li"}; // 单参数构造函数
s1.print(); // li 10
cout<<"**********"<<endl;
Student s2={"zhang",20}; // 多参数构造函数
s2.print(); // zhang 20
return 0;
}
explicit 关键字 / 单参数隐式转换
对于单参数构造函数可以使用下面的方法进行实例化
如果构造函数只有一个参数,可将对象初始化为一个与参数的类型相同的值,且自动调用构造函数
这相当于一种隐式类型转换: 编译器会先拿Base构造一个临时对象temp,然后将10作为参数传给这个临时对象temp,在拿b(temp)进行拷贝构造,也就相当于发生隐式类型转换,即先构造在进行拷贝构造
注意:单参数赋值必须有对应的构造函数className(int val)
不能是默认构造函数className()
(只定义默认构造函数,不能进行赋值运算)
注意,只有单参数的可以直接使用
clsType=val
,对于多参数的类,只能使用clsType={val1,val2}
总结:
对于单成员变量类,可以使用obj={val}
或obj=val
两种方式,但是不能只定义默认构造函数(也就是默认的赋值运算符,只能生成的className(type val))
对于多成员变量类,只能使用obj={val1,val2}
#include <iostream>
using namespace std;
class Base
{
public:
Base(int val):val(val){};
void print(){cout<<this->val;}; // 自动成为内联函数
private:
int val;
};
int main()
{
Base b=10;
b.print(); // 10
return 0;
}
explicit
只能用于构造函数
对于不想让单参数的构造函数发生隐式转换,可以对构造函数加上explicit关键字进行修饰,表明该构造函数是显示的,不能进行隐式转换
explicit Base(int val):val(val){};
例子
下面的例子,不能运行,报错
#include <iostream>
#include <string>
using namespace std;
class Student
{
private:
string name;
int age;
public:
explicit Student(string name);
explicit Student(string name,int age);
void print();
};
Student::Student(string name)
:name(name),age(10)
{
// this->age=10;
cout<<"单参数构造函数"<<endl;
}
Student::Student(string name,int age)
:name(name),age(age)
{
// this->age=age;
// this->name=name;
cout<<"多参数构造函数"<<endl;
}
void Student::print()
{
cout<<this->name<<" "<<this->age<<endl;
}
int main()
{
Student s1={"li"}; // 单参数构造函数
s1.print(); // li 10
cout<<"**********"<<endl;
Student s2={"zhang",20}; // 多参数构造函数
s2.print(); // zhang 20
return 0;
}
析构函数
销毁对象时系统会自动调用一个函数来进行清理
特殊的成员函数,没有返回值,不需要显示调用
析构函数与类名重名~class_name
析构函数没有参数,不能被重载
一个类只能有一个析构函数,如果用户没有定义,编译器会自动生成一个默认析构函数
自定义的析构函数用来释放已经分配的内存
只有调用delete的时候才会执行析构函数
在栈上的会自动调用析构函数
在堆上的必须使用delete再回执行析构函数,即使程序运行完毕,也不会调用用户自定义的析构函数
class MyClass
{
private:
const int m_len; // 数组长度
int *m_arr; // 数组指针
int *m_p; // 指向数组的第i个元素的指针
int *getPtrIdx(int i); // 获取第i个元素的指针
public:
MyClass(int len);
void input(); // 从控制台输入数组元素
~MyClass();
};
MyClass::MyClass(int len)
: m_len(len) //
{
if (len>0)
{
m_arr=new int[len];
}
else
{
m_arr=NULL;
}
}
// 这个函数很重要!!!
// m_p=getPtrIdx(i) 相当于判断是不是NULL(表达式的值是返回值)
// *(p+i)
void MyClass::input()
{
for (int i=0;m_p=getPtrIdx(i);i++)
{
cin>>*(getPtrIdx(i));
}
}
int * MyClass::getPtrIdx(int i)
{
if (!m_arr || i<0 || i>m_len)
{
return NULL;
}
else
{
return m_arr+i; // 指针+idx,跨过idx个dataType类型
}
}
MyClass::~MyClass()
{
delete[] m_arr; // 释放数组
}
int main()
{
MyClass *ptr=new MyClass;
delete ptr;
}
类的嵌套 - 类的成员对象
一个类A的对象是 类B的成员变量,则称为类的的成员对象
补充初始化列表
必须使用列表初始化的方式对 成员对象 进行初始化(调动其构造函数),如果初始化列表当中没有对其进行初始化,且该类又没有默认构造函数(无参构造函数或全缺省构造函数)则不能实现该成员对象的初始化
先执行类的成员对象的构造函数,再调用自身的构造函数
对于析构函数,先调用类的析构函数,再调用类的成员对象的析构函数
class C1
{
public:
C1(int x,int y);
void print() const;
private:
int m_x;
int m_y;
};
C1::C1(int x,int y):m_x(x),m_y(y)
{
}
void C1::print()const
{
cout<<"x:"<<this->m_x<<endl;
cout<<"y:"<<this->m_y<<endl;
}
class C2
{
private:
float m_z;
public:
C2(float z=2);
void print() const;
};
C2::C2(float z):m_z(z)
{
}
void C2::print()const
{
cout<<"z:"<<this->m_z<<endl;
}
class MyClass
{
private:
int m_p;
C1 m_itemC1;
C2 m_itemC2;
public:
MyClass(int p,int x,int y);
void print() const;
};
MyClass::MyClass(int p,int x,int y):m_p(p),m_itemC1(x,y)
{
// m_p=p;
// 当没有初始化列表的时候,这种方法时错误的
// m_itemC1=C1(x,y);
}
void MyClass::print()const
{
cout<<"p:"<<this->m_p<<endl;
this->m_itemC1.print();
this->m_itemC2.print();
}
int main()
{
MyClass m_class=MyClass(3,4,5);
m_class.print();
return 0;
}
静态成员
- 静态成员变量
- 静态成员函数
不同的实例对象 占用不同的内存
静态成员变量
(相当于类属性)只分配一份内存,且在全局数据区
静态成员变量的内存即不是在声明类时分配,也不是在创建对象时分配,而是在(类外)初始化时分配(编译的时候就已经分配了),也就是没有在类外初始化的静态成员变量不能使用
静态成员变量不占实例对象的内存,而是在所有对象之外开辟内存,即使不创建对象也可以访问
静态成员变量必须在类声明的外部进行初始化(因为在全局数据区,当不赋值的时候,默认初始化为0);
public、private、protected修饰的静态变量都要在类外进行初始化
初始化的时候不能在使用static关键字
注意: 静态成员变量,在头文件中(类声明文件中)进行声明,在方法文件中(对应的cpp文件中)进行初始化(这是因为类声明位于头文件中,程序可能将头文件包含在其他多个文件中,如果在头文件中进行初始化,将出现多个初始化语句副本,从而引发错误)
// xxx.h
class cls
{
static int a;
};
// xxx.cpp
int cls::a=20;
在实例对象之间共享数据
静态成员变量即可以通过实例对象访问,也可以通过类名访问,但是要遵循public,private,protected的访问机制
(对于public的静态成员变量,可以通过类名、实例对象名进行访问;对于private的静态成员变量,不可通过类名、实例对象名进行访问)
实例对象可以访问静态成员函数、静态成员变量
class Student
{
private:
static int num;
char *name;
int age;
public:
Student(char *name,int age);
void getNum();
~Student();
};
Student::Student(char *name,int age)
{
this->name=name;
this->age=age;
this->num++;
}
void Student::getNum()
{
cout<<Student::num<<endl;
}
Student::~Student()
{
}
int Student::num=0;
int main()
{
Student stu1=Student("x",1);
stu1.getNum(); // 1
Student stu2("y",2);
stu2.getNum(); // 2
Student *stu3=new Student("z",3);
stu3->getNum(); // 3
// 匿名对象
(new Student("k",4))->getNum(); // 4
return 0;
}
// ERROR
//private:
// int len=10;
// char arr[len]; // 这里len还没有分配内存空间,只是一个符号
// 下面是正确的
private:
static int len=10;
char arr[len];
例子2
#ifndef __STACK_H__
#define __STACK_H__
typedef unsigned long Item;
class Stack
{
private:
static const int MAX=12; // 这样写是正确的
Item items[MAX];
int top;
public:
Stack(/* args */);
~Stack();
};
// const int Stack::MAX=10;
/*
// 下面是错误的
类内
private:
static const int MAX;
Item items[MAX];
类外
const int Stack::MAX=10;
*/
Stack::Stack(/* args */)
{
}
Stack::~Stack()
{
}
#endif
静态成员函数
静态成员函数只能访问静态成员变量、静态成员函数
静态成员函数可以通过类名来直接调用,但是编译器不会为它增加形参this,不需要当前对象的地址(所以不管有没有创建对象,都可以调用静态成员函数),(相当于类方法)
(编译器在编译一个普通成员函数时,会隐式增加一个形参this,并把当前对象的地址赋值给this,所以普通函数只能通在创建实例对象后通过对象来调用,即需要当前对象的地址)
静态成员函数再定义时也不能再使用static关键字
注意:
this
指针不能在static静态成员函数中使用
相当于静态方法
实例对象可以访问静态成员函数、静态成员变量(也就是可以用this调用静态成员函数和静态成员变量,但是不能在静态成员函数中使用this)
class Student
{
private:
static int num;
char *name;
int age;
public:
Student(char *name,int age);
void getNum();
static int static_getNum();
~Student();
};
Student::Student(char *name,int age)
{
this->name=name;
this->age=age;
this->num++;
}
void Student::getNum()
{
cout<<Student::num<<" "<<this->num<<endl;
}
int Student::static_getNum()
{
cout<<"static "<<Student::num<<endl;;
return num;
}
Student::~Student()
{
}
int Student::num=0;
int main()
{
Student stu1=Student("x",1);
stu1.getNum();
Student stu2("y",2);
stu2.getNum();
Student *stu3=new Student("z",3);
stu3->getNum();
// 匿名对象
(new Student("k",4))->getNum();
int static_func_var1=stu1.static_getNum();
int static_func_var2=Student::static_getNum();
cout<<"static_func_var1:"<<static_func_var1<<"\t"<<"static_func_var2:"<<static_func_var2<<endl;
return 0;
}
const 成员
- const 成员变量 :见上面,const成员变量的初始化
- const 成员函数
- const 成员对象
// 下面是错误的
class cls
{
private:
const int num=0; // 这样写是不行的,声明的时候没有创建一个num对象
char arr[num]; // 这样写是不行的
};
// 下面是正确的
class cls
{
private:
static const int num=0; // 这样写是不行的,声明的时候没有创建一个num对象
char arr[num]; // 这样写是不行的
};
// 下面是正确的
class cls
{
private:
enum {num=0}; // 这样写是不行的,声明的时候没有创建一个num对象
char arr[num]; // 这样写是不行的
};
const 成员函数
const成员函数可以使用类中的所有成员变量,但是不能修改他们的值(不管成员是否具有const性质,都是不可修改的)
const成员函数要在声明和定义中都要加上const
const成员函数的调用,要根据public、private、protected的限制
本质上是
const classType* const this
public:
void getItem() const;
// 类外
void ClassName::getItem() const
{
/*code*/
}
例子
在下面的例子中,
Base & operator-(const Base & b) const;
该运算符重载会报错,因为const关键字修饰,使得不能被修改
但是对于 Base operator+(const Base & b) const; 本质上是利用了拷贝构造函数,重新修改了对象
/* .h */
#ifndef __TEST97_H__
#define __TEST97_H__
#include <iostream>
using namespace std;
class Base
{
private:
int val;
public:
Base(/* args */):val(10){};
Base(int val):val(val){};
~Base();
void getVal(){cout<<this->val;};
Base operator+(const Base & b) const;
Base & operator-(const Base & b) const;
// friend Base operator*(const Base & b);
};
#endif
/* .cpp */
#include "test97.h"
Base Base::operator+(const Base & b) const
{
Base temp;
temp.val=this->val+b.val;
return temp;
}
// 下面这个重载函数会报错
Base & Base::operator-(const Base & b) const
{
this->val+=b.val;
return *this;
}
// Base operator*(const Base & b)
// {
// }
Base::~Base()
{
}
/* main.cpp */
#include <iostream>
#include "test97.h"
using namespace std;
int main()
{
Base b1;
Base b2={10};
b1=b1+b2;
b1.getVal();
return 0;
}
const 实例对象/成员对象
https://blog.csdn.net/Superman___007/article/details/116236597
const 实例对象只能调用该类(实例对象所属的类)的const成员(const成员变量、const成员函数)
// 下面两种方法等价
const class instance(params);
class const instance(params);
// 修饰const对象
const class *p=new class(params);
class const *p=new class(params);
下面会报错
#include <iostream>
using namespace std;
class Base
{
public:
Base(a):a(a){};
void print(){cout<<this->a;}; // void print() const {cout<<this->a;} 修改成const成员函数就可以了
private:
int a;
}
int main()
{
const Base b=Base(10);
b.print(); // ERROR print函数中并没有保护变量被修改的机制!!!
return 0;
}
typedef 成员
typedef 定义的类型只能通过类名来访问
class Myclass
{
public:
typedef int INT;
static void staticFunc();
void func();
};
void Myclass::staticFunc()
{
cout<<"static func"<<endl;
}
void Myclass::func()
{
cout<<"func"<<endl;
}
int main()
{
Myclass mc;
mc.staticFunc();
mc.func();
Myclass::staticFunc();
Myclass::INT n=10;
return 0;
}
类的作用域
每个类都会定义自己的作用域,在类的作用域之外,普通的成员只能通过实例对象来访问,静态成员可以通过对象或类名访问,typedef 定义的类型只能通过类名访问
定义在类外部的(该类)成员,必须指明所属的类作用域
class Myclass
{
public:
typedef int INT;
int func(int a);
private:
int a;
};
Myclass:: INT Myclass::func(int a)
{
this->a=a;
return this->a;
}
int main()
{
Myclass mc;
mc.func(10);
return 0;
}
另外一个例子 - 使用全局变量
int num=10;
class Myclass
{
private:
int n;
int &r;
public:
Myclass();
};
Myclass::Myclass()
:n(0),r(num)
{
}
int main()
{
return 0;
}
类域进阶/this私有成员 – 还没看 很重要
https://blog.csdn.net/catwan/article/details/85335917
https://blog.csdn.net/weixin_48524215/article/details/115641607
https://flushhip.blog.csdn.net/article/details/80310327
https://www.cnblogs.com/fiteg/archive/2012/01/31/2332632.html
https://blog.csdn.net/qq_28110727/article/details/77071066
友元
其他类中的成员函数以及全局范围内的函数访问当前类的private成员
一个函数可以被多个类声明为
友元函数
友元函数可以是不属于任何类的非成员函数,也可以是其他类的成员函数
友元函数可以访问当前类中的所有成员,包括public,protected,private
友元函数可以访问类的私有成员,和私有成员函数
值得注意的是,友元函数 func() 不能使用const进行修饰
例子1 类的成员函数作为友元函数
值得注意的是:定义在类中的友元函数,并不是成员函数
但是虽然友元函数不是成员函数,但是与其他成员函数的访问权限相同,遵循public, protected,private原则
在定义友元函数的时候,不能加类作用域运算符,且只在声明中使用friend关键字,不能在定义中使用friend关键字
/*.h*/
#ifndef __TEST97_H__
#define __TEST97_H__
#include <iostream>
using namespace std;
class Base
{
private:
int val;
public:
Base(/* args */):val(10){};
Base(int val):val(val){};
~Base();
void getVal(){cout<<this->val<<endl;};
Base operator+(const Base & b) const;
// Base & operator-(const Base & b) const; // 会报错 const不能修改
friend Base operator*(const Base &a,const Base & b);
};
#endif
#include "test97.h"
Base Base::operator+(const Base & b) const
{
Base temp;
temp.val=this->val+b.val;
return temp;
}
// Base & Base::operator-(const Base & b) const 错误的
// {
// this->val+=b.val;
// return *this;
// }
// 友元函数 可以访问私有变量
Base operator*(const Base &a,const Base & b)
{
Base temp;
temp=a.val*b.val;
return temp;
}
Base::~Base()
{
}
/*main.cpp*/
#include <iostream>
#include "test97.h"
using namespace std;
int main()
{
Base b1;
Base b2={10};
b1=b1+b2;
b1.getVal();
Base b3;
b3=b1*b2;
b3.getVal();
return 0;
}
例子2 - 非成员函数作为友元函数
class Student
{
private:
char *name;
int age;
public:
Student(char *name,int age);
friend void show(Student *pstu);
~Student();
};
Student::Student(char *name,int age)
{
this->name=name;
this->age=age;
}
Student::~Student()
{
}
void show(Student *pstu)
{
// 通过友元函数访问私有成员
// 友元函数不能直接访问类的成员,必须借助实例对象
cout<<pstu->name<<",age: "<<pstu->age<<endl;
}
int main()
{
Student stu1("li",18);
show(&stu1);
Student *pstu=new Student("zhang",19);
show(pstu);
return 0;
}
例子3 - 其他类的成员函数作为友元函数
类的提前声明
类的提前声明的使用范围是有限的,只有在正式声明(class Myclass{/*code*/};
)一个类之后才能用它去创建对象
因为创建对象时位对象分配内存,在正式声明类之前,编译器无法确定应该为对象分配多大的内存,编译器只有在见到类的正式声明后(其实是见到成员变量),才能确定应该为对象预留多大的内存
在对一个类做了提前声明后,可以用该类的名字去定义指向该类型对象的指针变量,因为指针变量或引用变量本身的大小是固定的,与它所指向的数据的大小无关
// 必须提前声明,因为Student中使用了Myclass
class Myclass; // 提前声明一个类
class Student
{
private:
char *name;
int age;
public:
Student(char *name,int age);
void show(Myclass *mc);
~Student();
};
Student::Student(char *name,int age)
{
this->name=name;
this->age=age;
}
// 此时在Myclass 之前定义友元函数是错误,的因为没有找到该类的私有成员
// void Student::show(Myclass *mc)
// {
// // 将该函数定义为Myclass类的友元函数
// // 所以可以访问Myclass类中的私有成员
// cout<<this->name<<"'s grade:"<<mc->grade<<endl;
// }
Student::~Student()
{
}
class Myclass
{
private:
char *grade;
public:
Myclass(char *grade);
friend void Student::show(Myclass * mc);
~Myclass();
};
Myclass::Myclass(char *grade)
{
this->grade=grade;
}
Myclass::~Myclass()
{
}
// 在这写才是对的
void Student::show(Myclass *mc)
{
// 将该函数定义为Myclass类的友元函数
// 所以可以访问Myclass类中的私有成员
cout<<this->name<<"'s grade:"<<mc->grade<<endl;
}
int main()
{
Student stu1("li",18);
Myclass mc1("2");
stu1.show(&mc1);
Student *pstu=new Student("zhang",19);
Myclass *mc2=new Myclass("3");
pstu->show(mc2);
return 0;
}
例子4 - << 运算符重载
只能使用友元函数的方式进行重载
void operator<<(ostream &os , clsType &obj)
{
os<<obj.val<<endl;
}
连续输出
ostream & operator<<(ostream &os , clsType &obj)
{
os<<obj.val;
return os;
}
友元类 – 还没看
一般友元类不常用
拷贝构造函数
对象创建的两个阶段:分配内存空间,再进行初始化
初始化才是调用构造函数
拷贝构造,就是在初始化化阶段进行的,用其他对象的数据来初始化新对象的内存
当以拷贝的方式初始化一个对象时,会调用一个特殊的构造函数,就是拷贝构造函数
注意:下面两个可以构成重载,不会发生问题
cls(cls &cls_instance)
cls(const &cls_instance)
当用户没有显示的定义拷贝构造函数的时候,会自动生成一个默认的拷贝构造函数:
默认拷贝构造函数:使用老对象的成员变量对新对象的成员变量赋值(默认拷贝构造函数,一般应对简单的初始化操作)
cls(const cls &cls_instance)
默认生成
默认拷贝构造函数一般是够用的,没有必要显示的定义一个功能类似的拷贝构造函数,但是当类持有其他资源时,如动态分配的内存、打开的文件,指向其他数据的指针、网络连接等,默认拷贝构造函数就不能拷贝这些资源,必须显示的定义拷贝构造函数
例子1
str2、str3、str4都是拷贝构造函数
#include <iostream>
#include <string>
using namespace std;
void func(string str)
{
cout<<str<<endl;
}
int main()
{
string str1="zhangsan";
string str2(str1);
string str3=str1;
string str4=str1+" "+str2;
func(str1);
func(str2);
func(str3);
func(str4);
return 0;
}
例子2
值得注意的是,赋值运算符的重载,和调用拷贝构造函数 的区别
#include <iostream>
#include <string>
using namespace std;
class Student
{
private:
string name;
int age;
public:
Student(string name,int age);
Student(const Student &stu); // 拷贝构造函数声明
void print();
};
Student::Student(string name,int age)
{
this->age=age;
this->name=name;
}
Student::Student(const Student &stu)
{
this->age=stu.age;
this->name=stu.name;
cout<<"拷贝拷贝构造"<<endl;
}
void Student::print()
{
cout<<this->name<<" "<<this->age<<endl;
}
int main()
{
Student stu1("zhangsan",10);
Student stu2=stu1; // 调用的是拷贝构造函数,即使没有进行赋值运算符的重载,也可以调用拷贝构造函数
Student stu3(stu1);
stu1.print();
stu2.print();
stu3.print();
return 0;
}
例子3
Base::Base(const Base &instance)
{
/*
这里虽然是私有成员,但是拷贝构造函数传入来一个实例对象的引用相当于*this
那么就相当于在类内访问私有成员,知道作用域在哪
*/
this->a=instance.a;
this->b=instance.b;
this->count++;
this->time=std::time((time_t *)NULL);
}
拷贝与赋值
注意:拷贝构造函数和赋值的区别
本质上是 初始化与赋值的区别
初始化:定义的同时赋值
赋值:只是赋值
即使没有显示的重载
=
赋值运算符,编译器也会以默认的方式重载它。默认重载的赋值运算符功能很简单,就是将原有对象的所有成员变量赋值给新对象,这和默认拷贝构造函数的功能类似
和默认拷贝构造函数类似,对于简单的类,默认的赋值运算符足够了,但是对于动态分配的内存、打开的文件,指向其他数据的指针、网络连接等,默认赋值运算符就不行了
例子
#include <iostream>
#include <string>
using namespace std;
class Student
{
private:
string name;
int age;
public:
Student(string name,int age);
Student(const Student &stu); // 拷贝构造函数声明
Student & operator=(const Student &stu);
void print();
};
Student::Student(string name,int age)
{
this->age=age;
this->name=name;
cout<<"构造函数"<<endl;
}
Student::Student(const Student &stu)
{
this->age=stu.age;
this->name=stu.name;
cout<<"拷贝构造"<<endl;
}
Student & Student::operator=(const Student &stu)
{
this->name=stu.name;
this->age=stu.age;
cout<<"operator 重载"<<endl;
return *this; // !!!
}
void Student::print()
{
cout<<this->name<<" "<<this->age<<endl;
}
int main()
{
Student stu1("zhangsan",10); // 构造函数
cout<<"*****"<<endl;
Student stu2=stu1; // 调用的是拷贝构造函数 // 拷贝构造
cout<<"*****"<<endl;
Student stu3(stu1); // 拷贝构造
cout<<"*****"<<endl;
stu1.print(); // zhangsan 10
stu2.print(); // zhangsan 10
stu3.print(); // zhangsan 10
cout<<"*****"<<endl;
Student s1("lisi",20); // 构造函数
cout<<"*****"<<endl;
Student s2("wangwu",30); // 构造函数
cout<<"*****"<<endl;
Student s3=s1; // 拷贝构造
s3=s2; // operator 重载
cout<<"*****"<<endl;
Student s4=Student("zhao",30); // 构造函数+ 拷贝构造
return 0;
}
拷贝构造与函数
函数参数传递
函数内的局部参数、形参,只有在栈上分配内存,也就是在函数声明和定义的时候参数没有被创建
下面在参数传递的时候也会调用拷贝构造函数
void func(clsType cls_instance){}
clsType cls_instance;
func(cls_instance)
函数返回值
对于返回值是结构体和类对象时(值传递),为了防止局部对象被销毁,也为了防止通过返回值修改原来的局部对象,编译器并不会返回这个对象,而是根据这个对象先创建一个临时对象(匿名对象)以拷贝的方式进行,然后再将这个临时对象返回
下面在函数返回的时候也会调用拷贝构造函数
clsType func()
{
clsType cls_instance
return cls_instance;
}
clsType cls_instance=func();
例子
g++ file.cpp -o file -fno-elide-constructors
取消编译优化
#include <iostream>
#include <vector>
#include <string>
using namespace std;
class Base
{
private:
vector<string> names;
public:
Base();
Base(const Base &b);
~Base();
};
Base::Base()
:names(vector<string>{"li"})
{
cout<<"默认构造函数, size: "<<names.size()<<endl;
}
Base::Base(const Base &b)
{
this->names=b.names;
cout<<"拷贝构造函数, size: "<<names.size()<<endl;
}
Base::~Base()
{
cout<<"析构函数"<<endl;
}
Base func()
{
Base b;
return b;
}
int main()
{
Base obj=func();
return 0;
}
/*
默认构造函数, size: 1
拷贝构造函数, size: 1 // 局部变量到临时变量
析构函数
拷贝构造函数, size: 1 // 临时变量到主函数变量
析构函数
析构函数
*/
移动构造函数
见 ‘c++ 进阶其他’
对象与数组
实例对象数组
class MyClass
{
public:
MyClass();
MyClass(int n);
MyClass(int n,int m);
~MyClass();
};
MyClass::Myclass()
{
cout<<"init func1"<<endl;
}
MyClass::Myclass(int n)
{
cout<<"init func2"<<endl;
}
MyClass::MyClass(int n,int m)
{
cout<<"init func3"<<endl;
}
int main()
{
MyClass arr1[2]; // 数组中的两个实例对象都调用无参构造函数
MyClass arr2[2]={1,2}; // 数组中的两个实例对象都调用有参构造函数 (列表初始化)
MyClass arr3[2]={1}; // 数组中的两个实例分别调用有参构造函数和无参构造函数 (列表初始化)
MyClass *ptr=new MyClass[2]; // 数组中的两个实例都调用无参构造函数
MyClass arr4[2]={1,MyClass(2,3)};//分别调用(1参数构造函数)和(2参数构造函数)
MyClass arr5[2]={MyClass(1,2),MyClass(3,4)};
// 见C语言指针数组
Myclass* ptr2[3]={new MyClass(1,2),new MyClass(3,4)} // 相当于指针数组,指针所指向的数组中的每个元素都是一个指针(匿名对象)
delete[] ptr; // 这个很重要 只要出现new就要进行释放
return 0;
}
对象与指针
class Name
{
public:
void method();
};
void method()
{
}
// 实际上
instance.method() 实际上是 method(&instance)
method(class_name * const p) 也就是将instance的地址赋值给常量指针 class_name *p=&instance
this指针
this
指针是一个const 指针,不能被修改
this
指针,实际上存放的是对象的首地址,也就是指向当前对象
this
指针只能在类的内部使用
this
指针不能在static静态成员函数中使用
非静态函数中的this
指针可以访问静态成员变量(相当于实例对象名访问静态成员变量)
利用this
指针不用对变量名进行刻意的编写,也就是可处理 成员变量和形参变量重名的问题
class MyClass
{
private:
int a;
void setItem(int a);
};
void MyClass::setItem(int a)
{
this->a=a; // 指向当前的对象
}
int main()
{
// MyClass C;
// C.setItem(10);
MyClass *C=new MyClass();
C->setItem(10);
return 0;
}
#include <iostream>
using namespace std;
class Student{
public:
int getAge(){
return age;
}
void setAge(int age){
this->age=age; // this->age 表示的是当前类中的属性,(特别用于属性与形参名相同的时候)
// this->age 有点像self.age的作用
}
// 第二种用法
Student setAge(int age){
this->age=age;
return *this; // this指针,实际上存放的是对象的首地址,引入如果要返回一个类对象,所以要*this
}
private:
int age;
}; // 注意分号
int main(void)
{
Student s;
s.setAge(3);
cout<<s.getAge()<<endl;
return 0;
}
this指针的机制
this
实际上是成员函数的一个形参,在调用成员函数时将对象的地址作为实参传递给this
,不过this这个形参是隐式的,并不出现在代码中,而是在编译阶段由编译器默默的将他添加到参数列表中
this
作为隐式形参,本质上是成员函数的局部变量,所以只能在成员函数的内部,并且只有在通过对象调用成员函数时才给this赋值
#include <iostream>
using namespace std;
class Student{
public:
int getAge(){
return age;
}
// void setAge(int age){
// this->age=age; // this->age 表示的是当前类中的属性,(特别用于属性与形参名相同的时候)
// }
Student setAge(int age){
this->age=age;
return *this;
}
void test(){
cout<<"this 指针存放的是谁的地址"<<this<<endl; //0x61fe1c
}
/*
实际上编译器是自动加上了一个该对象类型的 指针
void test(Student* this){
cout<<"this 指针存放的是谁的地址"<<this<<endl; //0x61fe1c
}
*/
private:
int age;
}; // 注意分号
int main(void)
{
Student s;
// s.setAge(3);
// cout<<s.getAge()<<endl;
s.test();
/*
调用的时候相当于传入了这个对象的地址,void test(Student* this)
s.test(&s)
*/
cout<<"s 实例的地址"<<&s<<endl; //0x61fe1c
return 0;
}
静态成员函数不能访问this指针
// static void lazy(){
// cout<<this->age<<endl; // 静态成员函数不能使用this函数
// }
class/struct
c语言中 struct 不能包含成员函数
c++中 struct 可以包含成员函数
class 类中成员默认是private属性;类的继承默认是private继承
struct 结构体中成员默认是public属性;struct继承默认是public继承
class 可以使用模板
struct 不能使用模板
建议在c++中,class定义类,struct中定义结构体,不要进行混用
拷贝
- 深拷贝
- 浅拷贝
默认拷贝构造函数一般是够用的,没有必要显示的定义一个功能类似的拷贝构造函数,但是当类持有其他资源时,如动态分配的内存、打开的文件,指向其他数据的指针、网络连接等,默认拷贝构造函数就不能拷贝这些资源,必须显示的定义拷贝构造函数
深浅拷贝细节
int a=10;
int b=a; // 浅拷贝,虽然是内存地址不同,指向的数据也不同(应该叫做不完全拷贝)
classType class_instanceA;
classType class_instanceB=class_instanceA; // 默认拷贝构造函数,属于浅拷贝,实际上是不完全拷贝
例子
下面的例子中 如何直接使用默认拷贝构造函数,则是将arr1.p 直接赋值给arr2.p 导致arr2.p和arr1.p指向了同一块内存,所以会互相影响
#include <iostream>
using namespace std;
class Array
{
private:
int len;
int *p;
public:
Array(int len);
Array(const Array &arr);
~Array();
int operator[](int i) const;
int &operator[](int i);
int length() const;
};
Array::Array(int len)
{
this->len=len;
this->p=(int *)calloc(this->len,sizeof(int));
}
Array::Array(const Array &arr)
{
this->len=arr.len;
this->p=(int *)calloc(this->len,sizeof(int));
memcpy(this->p,arr.p,this->len*sizeof(int));
cout<<"拷贝构造"<<endl;
}
Array::~Array()
{
free(this->p);
}
int Array::operator[](int i) const
{
cout<<"重载1"<<endl;
return this->p[i];
}
int & Array::operator[](int i)
{
cout<<"重载2"<<endl;
return this->p[i];
}
int Array::length() const
{
return this->len;
}
void printArray(const Array &arr)
{
int len=arr.length();
if (len==0)
{
cout<<"empty"<<endl;
return ;
}
for (int i=0;i<len;i++)
{
if (i==len-1)
{
cout<<arr[i]<<endl;
}
else
{
cout<<arr[i]<<", ";
}
}
}
int main()
{
Array arr1(10);
for (int i=0;i<10;i++)
{
arr1[i]=i;
}
cout<<"********"<<endl;
Array arr2=arr1; // 注意这里调用的是拷贝构造函数 不是重载赋值运算符
arr2[5]=100;
cout<<"********"<<endl;
printArray(arr1);
printArray(arr2);
return 0;
}
深拷贝与浅拷贝
如果一个类拥有指针类型的成员变量,绝大部分情况下需要深拷贝,因为只有深拷贝才能将指向指针的内容再赋值一份出来,让原有对象和新生对象相互独立,不影响
通常情况下,如果类成员变量没有指针,浅拷贝就可以
另外需要深拷贝的情况就是在创建对象时进行一些预处理操作
例子2
#include <iostream>
#include <ctime>
#include <windows.h>
using namespace std;
class Base
{
private:
int a;
int b;
time_t time; // 对象创建时间
/*
静态成员变量,相当于类属性
私有成员,遵循私有成员访问权限
this 和类名都可以进行方法
要在类外进行初始化
*/
static int count; // 创建过的对象的数目,静态成员变量
public:
Base(int a=0,int b=0);
Base(const Base &instance);
int getCount() const;
time_t getTime() const;
};
int Base::count=0;
Base::Base(int a,int b)
{
this->a=a;
this->b=b;
this->count++;
this->time=std::time((time_t *)NULL);
}
Base::Base(const Base &instance)
{
/*
这里虽然是私有成员,但是拷贝构造函数传入来一个实例对象的引用相当于*this
那么就相当于在类内访问私有成员,知道作用域在哪
*/
this->a=instance.a;
this->b=instance.b;
this->count++;
this->time=std::time((time_t *)NULL);
}
int Base::getCount() const
{
return this->count;
}
time_t Base::getTime() const
{
return this->time;
}
int main()
{
Base instance(1,2);
cout<<instance.getCount()<<" "<<instance.getTime()<<endl;
Sleep(3000);
cout<<"********"<<endl;
Base instance2=instance; // 注意这调用的是拷贝构造函数,而不是重载赋值运算符
cout<<instance2.getCount()<<" "<<instance2.getTime()<<endl;
return 0;
}
赋值
注意:拷贝构造函数和赋值的区别
本质上是 初始化与赋值的区别
初始化:定义的同时赋值
赋值:只是赋值
即使没有显示的重载
=
赋值运算符,编译器也会以默认的方式重载它。默认重载的赋值运算符功能很简单,就是将原有对象的所有成员变量赋值给新对象,这和默认拷贝构造函数的功能类似
和默认拷贝构造函数类似,对于简单的类,默认的赋值运算符足够了,但是对于动态分配的内存、打开的文件,指向其他数据的指针、网络连接等,默认赋值运算符就不行了
例子
#include <iostream>
using namespace std;
class Array
{
private:
int len;
int *p;
public:
Array(int len);
Array(const Array &arr);
~Array();
int operator[](int i) const;
int &operator[](int i);
Array & operator=(const Array &arr); // 赋值运算符重载
int length() const;
};
Array::Array(int len)
{
this->len=len;
this->p=(int *)calloc(this->len,sizeof(int));
}
Array::Array(const Array &arr)
{
this->len=arr.len;
this->p=(int *)calloc(this->len,sizeof(int));
memcpy(this->p,arr.p,this->len*sizeof(int));
cout<<"拷贝构造"<<endl;
}
Array::~Array()
{
free(this->p);
}
int Array::operator[](int i) const
{
// cout<<"重载1"<<endl;
return this->p[i];
}
int & Array::operator[](int i)
{
// cout<<"重载2"<<endl;
return this->p[i];
}
int Array::length() const
{
return this->len;
}
Array & Array::operator=(const Array &arr)
{
if (this != &arr) // 判断是否是给自己赋值
{
this->len=arr.len;
free(this->p); // 释放原来的内存
this->p=(int *)calloc(this->len,sizeof(int));
memcpy(this->p,arr.p,this->len*sizeof(int));
}
cout<<"=重载"<<endl;
return *this;
}
void printArray(const Array &arr)
{
int len=arr.length();
if (len==0)
{
cout<<"empty"<<endl;
return ;
}
for (int i=0;i<len;i++)
{
if (i==len-1)
{
cout<<arr[i]<<endl;
}
else
{
cout<<arr[i]<<", ";
}
}
}
int main()
{
Array arr1(10);
for (int i=0;i<10;i++)
{
arr1[i]=i;
}
printArray(arr1);
cout<<"********"<<endl;
Array arr2(5);
for (int i=0;i<5;i++)
{
arr2[i]=i;
}
printArray(arr2);
cout<<"********"<<endl;
arr2=arr1;
return 0;
}
补充
类与枚举类型
在类中定义的枚举类型,作用域是整个类,因为枚举类型相当于符号常量
也就是只是创建了符号常量,并不是创建枚举类型的变量