个人 申请域名做网站,爱站网在线全集私人影视,php手机网站开发教程,wordpress 广告收入类和对象#xff1a;
类的引入#xff1a;
C语言结构体中只能定义变量#xff0c;在C中#xff0c;结构体内不仅可以定义变量#xff0c;也可以定义函数。比如#xff1a; 之前在数据结构初阶中#xff0c;用C语言方式实现的栈#xff0c;结构体中只能定义变量#…类和对象
类的引入
C语言结构体中只能定义变量在C中结构体内不仅可以定义变量也可以定义函数。比如 之前在数据结构初阶中用C语言方式实现的栈结构体中只能定义变量现在以C方式实现 会发现struct中也可以定义函数
typedef int DataType;
struct Stack
{//成员函数void Init(size_t capacity){_array (DataType*)malloc(sizeof(DataType) * capacity);if (nullptr _array){perror(malloc申请空间失败);return;}_capacity capacity;_size 0;}void Push(const DataType data){// 扩容_array[_size] data;_size;}DataType Top(){return _array[_size - 1];}void Destroy(){if (_array){free(_array);_array nullptr;_capacity 0;_size 0;}}//成员变量DataType* _array;size_t _capacity;size_t _size;
};
但c更喜欢用class来代替
类的两种定义方式
1. 声明和定义全部放在类体中需注意成员函数如果在类中定义编译器可能会将其当成内 联函数处理。
2. 类声明放在.h文件中成员函数定义放在.cpp文件中注意成员函数名前需要加类名::
这些在之后都会经常用到
成员变量命名规则
class Date{public:void Init(int year){// 这里的year到底是成员变量还是函数形参
year year;}private:int year;};
为了区分开来会在成员变量前加_来区分也可以用其他方式
类的访问限定符
1.pubic,公有 2.protected保护 3.private私有 1. public修饰的成员在类外可以直接被访问 2. protected和private修饰的成员在类外不能直接被访问(此处protected和private是类似的) 3. 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止 4. 如果后面没有访问限定符作用域就到}即类结束。 5. class的默认访问权限为privatestruct为public(因为struct要兼容C) 我们一般对成员变量设为私有成员函数设为公有
如何计算类的大小
一个类的大小实际就是该类中”成员变量”之和当然要注意内存对齐成员函数会放在公共的代码段因为多个对象调用同一份函数如果按照此种方式存储当一 个类创建多个对象时每个对象中都会保存一份代码相同代码保存多次浪费空间
但空类比较特殊编译器给了空类一个字节来唯一标识这个类的对象即证明这个类存在
class A
{};
int main()
{cout sizeof(A) endl;return 0;
} this指针
特点 1. this指针的类型类类型* const即成员函数中不能给this指针赋值 2. 只能在“成员函数”的内部使用 3. this指针本质上是“成员函数”的形参当对象调用成员函数时将对象地址作为实参传递给 this形参。所以对象中不存储this指针this存储在栈上 4. this指针是“成员函数”第一个隐含的指针形参一般情况由编译器通过ecx寄存器自动传 递不需要用户传 我们在类中也可以直接使用this指针来更好的理解
void Print()
{
// this nullptr;
cout this-_year - this-_month - this-_day endl;
cout _year - _month - _day endl;
}
private:
int _year; // 年
int _month; // 月
int _day; // 日
};int main()
{
Date d1;
Date d2;
d1.Init(2024, 4, 2);
d2.Init(2024, 4, 3);
}
this指针可以为空吗
public:void Print(){cout Print() endl;}private:int _a;};int main(){A* p nullptr;p-Print();return 0;} class A{
public:void PrintA() {cout_aendl;}private:int _a;};int main(){A* p nullptr;p-PrintA();return 0;} 以上两个代码第一个不会报错第二个报错原因是第一个没有对this指针解引用而第二个对this空指针解引用了会进行报错 空类中真的什么都没有吗并不是任何类在什么都不写时编译器会自动生成以下6个默认成员 函数。 默认成员函数用户没有显式实现编译器会生成的成员函数称为默认成员函数
类的6个默认成员函数 1.构造函数
构造函数的主要任务并不是开空间创建对象而是初始化对象
特征 1. 函数名与类名相同。 2. 无返回值。 3. 对象实例化时编译器自动调用对应的构造函数。 4. 构造函数可以重载。 class Date
{
public:Date()//无参,打印值默认{}Date(int year, int month, int day)//有参打印传参值{_year year;_month month;_day day;}void Print(){cout _year _month _day endl;}
private:int _year ;int _month;int _day;
};
int main()
{Date d1;// 注意如果通过无参构造函数创建对象时对象后面不用跟括号否则就成了函数声明d1.Print();Date d2(1, 2, 3);d2.Print();return 0;
} 如果类中没有显式定义构造函数则C编译器会自动生成一个无参的默认构造函数一旦 用户显式定义编译器将不再生成。 C把类型分成内置类型(基本类型)和自定义类型。内置类型就是语言提供的数据类 型如int/char...自定义类型就是我们使用class/struct/union等自己定义的类型对于内置类型没有规定要不要做处理部分编译器会处理对自定义类型才会调用无参构造
但
C11 中针对内置类型成员不初始化的缺陷又打了补丁即内置类型成员变量在 类中声明时可以给默认值 class Time{private:int _hour;int _minute;int _second;};
class Date{private:// 基本类型(内置类型)int _year 1;int _month 1;int _day 1;// 自定义类型
Time _t;};int main(){Date d;return 0;} 内置类型可处理可不处理自定义类型会调用默认构造如果没有就会报错可以进行自定义类型的多重嵌套
无参的构造函数和全缺省的构造函数都称为默认构造函数并且默认构造函数只能有一个。 注意无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数都可以认为是默认构造函数。否则会存在使用冲突
2.析构函数
特点 1. 析构函数名是在类名前加上字符 ~。 2. 无参数无返回值类型。 3. 一个类只能有一个析构函数。若未显式定义系统会自动生成默认的析构函数。注意析构 函数不能重载 4. 对象生命周期结束时C编译系统系统自动调用析构函数。 当没有显式析构函数时编译器自动生成的析构函数对内置类型不做处理自定义类型调用析构函数 class Time{public:~Time(){cout ~Time() endl;}private:int _hour;int _minute;int _second;};class Date{private:// 基本类型(内置类型)int _year 1970;int _month 1;int _day 1;// 自定义类型
Time _t;};int main(){Date d;return 0;} 如果类中没有申请资源时析构函数可以不写直接使用编译器生成的默认析构函数比如 Date类有资源申请时一定要写否则会造成资源泄漏比如Stack类 stack::~stack(){if (_a) {cout Destroy endl;_capacity 0;_top 0;free(_a);_a NULL;}}
3.拷贝构造 拷贝构造函数只有单个形参该形参是对本类类型对象的引用(一般常用const修饰)在用已存 在的类类型对象创建新对象时由编译器自动调用
特点
1. 拷贝构造函数是构造函数的一个重载形式。
2. 拷贝构造函数的参数只有一个且必须是类类型对象的引用使用传值方式编译器直接报错 因为会引发无穷递归调用。 class Date
{
public:void Print(){cout _year _month _day endl;}Date(int year1,int month1,int day1) {cout 构造 endl;_year year;_month month;_day day;}Date(const Date d)//防止被修改{cout 拷贝 endl;_year d._year;_month d._month;_day d._day;}
private:int _year1 ;int _month1;int _day1;
};
int main()
{Date d;d.Print();Date d1d;//Date d1(d);两者等价d1.Print();return 0;
}
拷贝构造参数用const 修饰防止被修改
为了理解无穷递归在这里举个例子
void func(Date d)
{d.Print();
}
int main()
{Date d2;func(d2);return 0;
}
这里用引用传递实参和形参都是d2,而值传递的话会先拷贝d2给d进入到拷贝函数中d2和d地址也不同
若未显式定义编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝这种拷贝叫做浅拷贝或者值拷贝。
类中如果没有涉及资源申请时拷贝构造函数是否写都可以一旦涉及到资源申请 时则拷贝构造函数是一定要写的否则就是浅拷贝。
如stack中的深拷贝 stack::stack(const stack st){_a (int*)malloc(sizeof(int) * st._capacity);if (_a nullptr){return;}_capacity st._capacity;_top st._top;memcpy(_a, st._a, _capacity * sizeof(int));//深拷贝}//不这样写只是浅拷贝 4.运算符重载
C为了增强代码的可读性引入了运算符重载运算符重载是具有特殊函数名的函数也具有其 返回值类型函数名字以及参数列表其返回值类型与参数列表与普通的函数类似
函数名字关键字operator后面接需要重载的运算符符号。
注意 不能通过连接其他符号来创建新的操作符比如operator 作为类成员函数重载时其形参看起来比操作数数目少1因为成员函数的第一个参数为隐 藏的this .* :: sizeof ?: . 注意以上5个运算符不能重载。 .*很少见下面给一个场景
class OB
{
public:void func(){cout void func() endl;}
};typedef void(OB::* PtrFunc)();//成员函数指针类型int main()
{PtrFunc fp OB::func;//定义成员函数指针p指向函数funcOB temp;//定义ob类对象temp(temp.*fp)();return 0;
}
运算符重载成全局的就需要成员变量是公有的封装性不能保证。
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 Date d2)
{return d1._year d2._year d1._month d2._month d1._day d2._day;
} 这里先写一个判断日期相等的函数可以看到如果写成全局的函数私有的变量就需要变为公有所以我们就重载成成员函数
这里可以用我们用重载成成员函数解决
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;
};
赋值运算符重载
格式 参数类型const T传递引用可以提高传参效率 返回值类型T返回引用可以提高返回的效率有返回值目的是为了支持连续赋值 检测是否自己给自己赋值 返回*this 要复合连续赋值的含义 对返回值类型进行深究
传值返回生成一个拷贝作为返回值
传引用返回不会拷贝
Date func()
{Date d;return d;//出作用域析构d
}//传值返回临时对象具有常性需要拷贝
int main()
{Date ref func();//报错const Date ref func();//正确return 0;
}
这里使用传值返回 在func中建立d返回d的值时会拷贝d的值给一个临时变量调用拷贝函数临时变量具有常性需要const 接收并且d的值,出函数作用域会调用析构
Date func()
{Date d;return d;
}
int main()
{Date ref func();//ref类似于野指针了指向的空间可能会改变return 0;
}
这里传引用是有问题的d出作用域就会销毁再返回其地址类似于野指针了指向空间可能会改变
但如果改成这样的话就不会报错了因为d相当于在全局域了
Date func()
{
static Date d;return d;
}//可以引用返回
所以总结一下 返回对象是局部变量或临时对象用引用返回存在风险虽然减少了拷贝 返回对象是全局变量用引用就行 出了作用域返回对象还在没有析构那就可以用引用返回减少拷贝 a、返回对象生命周期到了会析构传值返回 b、返回对象生命周期没到不会析构传引用返回 连续复制如果知识两个值进行运算的话我们是可以将返回类型设为void的但d1d2d3怎么办呢这个式子从右向左依次赋值所以返回类型要修改为Date Date operator(const Date d)//void不支持三个数连续等于//传值返回会拷贝构造{cout 赋值 endl;if (this ! d)//防止自己给自己赋值{_year d._year;_month d._month;_day d._day;}return *this;}
5..取地址及const取地址操作符重载
class A {
public:A* operator(){return this;}const A* operator()const {return this;}
};
int main()
{A a1;const A a2;cout a1 endl;cout a2 endl;return 0;
} 这两个函数重载依靠this指针类型的不同,但我们不写编译器会自动生成 日期类的实现
class Date
{
public:friend ostream operator(ostream out, const Date d);///ostream抢不过第一个参数形参//istream operator(const Date d);参数问题friend istream operator(istream in,Date d);Date(int year 1900, int month 1, int day 1);int CheckDate(){if (_month 12 || _month 1||_dayGet_day(_year,_month)){cout 输入错误 endl;return 1;}else{return 0;}}void Print();int Get_day(int year, int month){assert(month 0 month 13);static int data[13] { 0,31,28,31,30,31,30,31,31,30,31,30,31 };//多次调用if (month2(year%1000year%4!0||year%4000)){return 29;}return data[_month];}Date operator(int day);Date operator(int day)const;Date operator-(int day);Date operator-(int day)const;Date operator(int);Date operator--(int);Date operator--();Date operator(); bool operator(const Date d)const;bool operator(const Date d) const;bool operator(const Date d) const;bool operator(const Date d) const;bool operator(const Date d) const;bool operator!(const Date d) const;int operator-(const Date d) const;private:int _year;int _month;int _day;
};
Date::Date(int year, int month, int day)
{_year year;_month month;_day day;
}
void Date::Print()
{cout _year _month _day endl;
}
Date Date::operator(int day)
{if (day 0){*this--day;return *this;}_day day;while (_day Get_day(_year, _month)){_day - Get_day(_year, _month);_month;if (_month 13){_month 1;_year;}}return *this;
}
Date Date::operator(int day) const
{Date tmp *this;tmp day;return tmp;
}
Date Date::operator-(int day)
{if (day 0){*this -day;return *this;}_day - day;while (_day 0){--_month;if (_month 0){_month 12;_year--;}_day Get_day(_year, _month);}return *this;
}
Date Date::operator-(int day) const
{Date tmp *this;tmp - day;return tmp;
}
Date Date::operator(int)
{Date tmp *this;(*this) 1;return tmp;
}
Date Date::operator--(int)
{Date tmp *this;(*this)-1;return tmp;
}
Date Date::operator--()
{(*this) - 1;return *this;
}
Date Date::operator()
{(*this) 1;return *this;
}
bool Date::operator(const Date d) const
{if (_year d._year){return true;}else if (_year d._year){if (_month d._month){return true;}else if (_month d._month){if (_day d._day){return true;}}}return false;
}
bool Date::operator(const Date d) const
{return *this d || *this d;
}
bool Date::operator(const Date d)const
{return !(*this d);
}
bool Date::operator(const Date d)const
{return !(*this d);
}
bool Date::operator(const Date d)const{return _year d._year _month d._month _day d._day;}
bool Date::operator!(const Date d)const{return !(*this d);}
int Date:: operator-(const Date d)const//算有多少天
{Date big *this;Date small d;//拷贝构造int flag 1;if (big small){big d;small *this;flag -1;}int n 0;while (small big){small;n;}return n*flag;
}
ostream operator(ostream out, const Date d)
{out d._year d._month d._day endl;return out;
}
istream operator(istream in, Date d)//传引用不支持拷贝构造被禁用了
{cout 请输入年月日 endl;in d._year d._month d._day;if (d.CheckDate()){cout 日期非法 endl;}return in;
}日期类的实现有一些需要注意的点我在这里一一说明 1.对于多次调用的Get_day函数我们直接在类中展开写其会自动转化为内联函数包括data数组我们也要多次使用所以在前面加上static修饰变为全局变量 2.对于*this不能改变的函数我们在函数声明后加上const修饰比较特殊可以发现传引用返回并且对*this进行操作的一般不加const修饰 3.前置和后置都是一元运算符为了让前置与后置形成能正确重载 C规定后置重载时多增加一个int类型的参数但调用函数时该参数不用传递编译器 自动传递 4.cout和cin无法对自定义成员进行处理进行运算符重载要将其转化为友元类才符合输出格式重载为全局破坏封装性重载为成员函数不是友元输出格式不符合,第一个参数为this形参不能是cout或cin