目录
一、为什么要封装?
提高代码的稳定性。
以下代码可以看出,c++不同的编译器会有不同,有些编译器在数组越界时也不会报错和警告,直接打印数据,并且正确。我使用两小时测试了c++数组对字符串的界定。
例如如下代码:
#include<iostream>
#include<stdlib.h>
#include<string.h>
using namespace std;
int main()
{
char arr[5]={0};
strcpy(arr,"helloworld ");
strcat(arr,"goodgoodgood");
cout << arr << endl;
}
可以看出数组已越界,在c语言里面应该是打印不了的,会直接段错误,我们看看c++的处理;
它直接执行到最后的语句并打印了正确结果,有时候它对小于自己的字符串1可以接上,有时候又会段错误,飘忽不定的。因此需要封装一下。下面实现两个字符串的连接。
#include <iostream>
#include <string.h>
using namespace std;
//因为c++编译器问题,数组越界也能正常打印,所以需要封装
//以下代码可实现两个字符串计算并打印,实现c++封装特性
//实现类似strcat的代码
class str
{
public:
char *cat(char *str1, char *str2)
{
bzero(buf, sizeof(buf)); //清除上次缓冲区的数据
j = strlen(str1) + strlen(str2); //计算两个字符串长度
//如果两个字符串相加小于或等于缓冲区时,进行下面程序
if (j <= sizeof(buf))
{
strcat(buf, str1); //直接拷贝
strcat(buf, str2); //直接追加
return buf; //返回值
}
//如果第一个字符串>=缓冲区,或第二个字符串>=缓冲区大小时,进行下面程序
else
{
for (i = 0,k=0; i < sizeof(buf); i++) //缓冲区大小
{
buf[i] = str1[i]; //拷贝str1到buf中
if (str1[i] == '\0')
{ //判断str1到结尾
while (i<sizeof(buf))//进行第二个判断
{
buf[i] = str2[k]; //拷贝str2到buf中
i++;
k++; // k++遍历str2
}
break;
}
}
return buf; //返回strcat的值
}
}
private:
char buf[10] = {0};
int i = 0;
int j = 0;
int k = 0;
};
int main()
{
str p;
char *g = p.cat("hello", "world");
cout << g << endl;
char *g2 = p.cat("123", "456");
cout << g2 << endl;
}
一个好的封装可以避免产生内存泄露和数据被修改的重大错误,用户只能在有限的范围内访问,不会触发内部核心代码。
二、c++ 构造函数
作用: 初始化类中的数据成员,让一个对象创建后就立即可以使用。
构造函数特点:
1.函数名与类名相同
2.函数没有返回值。
3.函数不需要用户调用,在创建对象的时候自动调用。
4.分为有参构造和无参构造。
5.构造函数可以重载。
#include<iostream>
#include<string.h>
using namespace std;
//在构造函数初始化,在外部方法(函数)中调用这些对象(已初始化的变量)
class student{
public:
//无参构造函数
student(){
strcpy(name,"小方");
id = 1008611;
age = 20;
}
void show(){ //调用无参构造函数的对象
cout << "无参构造:" << endl;
cout << "姓名 " << "年龄 " << "id"<< endl;
cout << name << " " << age << " " << id << endl ;
}
//有参构造函数
student(char *name,int age,int id){
strcpy(this->name,name);
this->age = age; //this指针用法:左边this->定义的对象 = 传参进来的对象
this->id = id; //this指针作用:可以在该构造函数使用同名对象,在main函数定义一个变量即可
}
void show2(){ //调用有参构造函数的对象
cout << "有参构造:\n";
cout << "姓名 " << "年龄 " << "id"<< endl;
cout << name <<" "<< age <<" "<< id<< endl; //这里面加不加this->都可以
}
private:
char name[10];
int id;
int age;
};
int main()
{
student s;
s.show(); //无参调用
student k("小红",21,1008612); //有参调用
k.show2();
}
!this 实际上是成员函数的一个形参,在调用成员函数时将对象的地址作为实参传递给 this。不过 this 这个形参是隐式的,它并不出现在代码中,而是在编译阶段由编译器默默地将它添加到参数列表中。this 作为隐式形参,本质上是成员函数的局部变量,所以只能用在成员函数的内部,并且只有在通过对象调用成员函数时才给 this 赋值。
每一个类的内部,都有一个this 指针,指向当前类的首地址!作用: 区分类内成员与类外成员
(如果函数内对象不重名的话不用this指针也行,因为它的作用是使重名的变量区分开来)。
三、拷贝构造函数
1、当一个类定义时通过另一个类进行初始化,就会调用拷贝构造函数。 (系统自动生成的拷贝构造函数 : 浅拷贝)。
xbase c = b; //发生了隐式转换,在定义的时候初始化。
c.xb_show();
浅拷贝: 类中的数据成员全部都是单纯的值,用户直接调用系统自动生成的浅拷贝构造函数即可。
#include<iostream>
using namespace std;
class xbase{
public:
xbase(){cout << "无参构造函数\n";}
xbase(int data,int data2){
cout << "有参构造函数" << endl;
this->data2 = data2;
this->data = data;
}
xbase(const xbase &tmp){ //自定义拷贝构造函数(浅拷贝)
this->data = tmp.data;
this->data2 = tmp.data2;
}
void xb_show(){
cout << this->data << endl;
cout << this->data2 << endl;
}
private:
int data;
int data2;
};
int main()
{
xbase a(10,20); //普通的有参调用
a.xb_show();
xbase b = a; //构造函数隐式转换,相当于base b(a);
cout << "拷贝构造函数." << endl;
b.xb_show();
xbase c = b; //系统自动生成拷贝构造函数(浅拷贝)!!发生了隐式转换
cout << "拷贝构造函数." << endl;
c.xb_show(); //在定义的时候初始化,没有调用有参构造函数
xbase d; //先定义,调用了无参构造。
d = a; //再调用赋值运算符,与拷贝函数无关
d.xb_show();
}
深拷贝: 当一个类中分配了 堆空间资源时,浅拷贝构造函数会造成,多个类共同指向一块堆空间。 造成资源的抢占或者重复释放。
#include<iostream>
#include<string.h>
using namespace std;
//由于浅拷贝的系统构造函数没有分配堆空间,因此需要深拷贝给拷贝函数分配空间
static int i=2;
class xbase{
public:
xbase(){cout << "我是无参构造函数\n";}
xbase(int data,int data2,const char *s){
cout << "有参构造函数" << endl;
this->data2 = data2;
this->data = data;
str = new char[1024];
strcpy(this->str,s);
cout << "申请str1地址:" << &(this->str) << endl;
}
xbase(const xbase &tmp){ //自定义拷贝构造函数(深拷贝)
//寻找格式:tmp.定义的变量
//基本是复制构造函数,只不过要调用一个引用参数
this->data = tmp.data;
this->data2 = tmp.data2;
this->str = new char[1024];
strcpy(this->str,tmp.str);
cout << "申请str2地址:" << &(this->str) << endl;
}
~xbase(){
delete []str;
cout << "释放str" <<i--<<"空间:"<< &(this->str) << endl;
}
void xb_show(){
cout << this->data << endl;
cout << this->data2 << endl;
}
private:
int data;
int data2;
char *str;
};
int main()
{
xbase s(10,20,"hello");
xbase p = s; //深拷贝,参数是base &tmp。引用格式:类 &参数
}
1、new和malloc在构造函数中的区别(最根本的不同)
#include<iostream>
#include<stdlib.h>
#include<string.h>
using namespace std;
// new和malloc的区别在于malloc不能调用构造函数,所有无法给类对象分配空间
class demo{
public:
demo(){ // 无参构造
cout << "我是无参构造函数!\n";
}
demo(int b){ //有参构造
data = b;
cout << "我是有参构造函数! data:" << data << endl;
}
private:
int data;
};
int main()
{
// new和malloc的区别
demo *p = (demo *)malloc(sizeof(demo)); //用malloc不能调用构造函数,也就不能使用类对象
demo *q = new demo; //默认调用无参构造函数
demo *q2 = new demo(10086); //调用有参构造函数
}
结果:![](https://i-blog.csdnimg.cn/blog_migrate/3cdc983c5867c5ddb137e882616c1a73.png)
总结:因为malloc 无法调用类中的构造函数,导致类中的数据成员无法初始化! new是c++新创的运算符,可以申请堆空间,也可以初始化。数据类型 *变量名 = new 数据类型[大小];delete()释放。
四、c++形参列表
参数列表初始化的方式有三种:第一种是在定义的时候直接初始化,第二种是用一个函数来初始化,第三种是用构造函数初始化。
*构造函数初始化的方式:
1、在函数内赋值部初始化,使用this指针(不重名就不用)。
2、使用参数列表初始化:
//参数列表初始化的方式,函数(参数,参数..):类内定义的对象(参数),类内定义的对象(参数)...
//参数列表初始化不能初始化char*型的数据
//()里面的是形参,外面的是成员变量,将()里面的形参赋值给外面的成员初始化。
#include<iostream>
#include<string.h>
using namespace std;
class student{
public:
student(){cout << "无参构造函数\n";}
//参数列表初始化的方式,函数(参数,参数..):类内定义的对象(参数),类内定义的对象(参数)...
//参数列表初始化不能初始化char*型的数据
//()里面的是形参,外面的是成员变量,将()里面的形参赋值给外面的成员初始化。
student(int age,int tall,const char *n):age(age),tall(tall){
strcpy(name,n);
}
void stu_show(){
cout << "姓名:" << name << " 身高:"<< tall << " 年龄:" << age << endl;
}
private:
int age;
int tall;
char name[10]={0};
};
int main()
{
student p(23,188,"小强"); //有参构造并声明一个类对象
p.stu_show();
}
五、析构函数
作用:在类对象消亡时释放该类内存,(例如:分配的动态内存空间,打开的文件,映射的空间...) 。
特点:1.、函数名与类名相同在前面加~
2、函数没有返回值,没有参数,因此不可以重载。
3、当对象销毁时候系统自动调用。(不用自己调用)
4、用new申请的类对象需要自己手动释放才能调用析构函数。(因为自由储存区的空间不能用函数周期来结束,因为函数存在栈区中)。
5、先建立的对象先用拷贝构造函数,后建立的后用,调用析构函数优先,当删除对象时会自动调用析构函数,先删除后建立的,再删除先调用的。(和栈一样,先进后出)。
下面用打开文件的例子来说明一下析构函数的用法:
#include<iostream>
extern "C"{
#include<stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
}
using namespace std;
class File{
public:
// File(){}
File(const char *f="hello.txt"){
fd = open(f,O_RDWR|O_CREAT);
if(fd<0){
cout << fd <<endl;
}
else{
printf("打开文件成功 %d\n",fd);
}
str = new char[1024];
cout << "申请动态内存\n" ;
}
~File(){
close(fd);
delete []str;
str = NULL;
cout << "已释放动态内存str:" << str << endl;
}
private:
int fd;
char * str;
};
//等待所有构造函数执行完才调用析构函数
void test(){
File f; //默认打开hello.txt,对象消亡,自动调用析构函数
File f1("good.txt"); //对象消亡,自动调用析构函数
File f2("tes.txt");
File f3("tes2.txt");
File *f4 = new File;
delete f4;
cout << "该函数周期已结束,开始调用析构函数:\n";
}
int main()
{
int i=10;
while (i--)
{
test();
}
}