1.类和对象
在C语言中,结构体只能用来定义变量,而在C++中,结构体不仅可以定义变量,还可以定义函数。而C++更喜欢使用class来代替struct使用。
C语言是面向过程的语言,它的对象仅仅是一些变量,它更偏向过程,而函数的实现就是对过程的实现。在C++中,将问题分成一个一个对象,不仅是由变量构成对象,函数更是对象的各种动作的实现。
2.类的定义
类里既能定义变量,也能定义函数
class Student
{
void getInfor()
{
printf("学生名字:%s\n", _name);
printf("学生年龄:%d\n", _age);
}
char _name[10];
int _age;
};
3.封装
封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。
3.1 访问限定符
C++中使用访问限定符可以更好的管理对象中的数据和方法,在对象内部可以操作自己对象的数据,而对外部通过访问权限选择性的将其接口提供给外部的用户使用。
访问限定符: 1:public修饰的成员可以被外部直接访问 2:protected和private修饰的成员在类外不能直接被访问 3:访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止 4:class的默认访问权限为private,struct为public
例:
struct Student
{
public:
void getInfor()
{
printf("学生名字:%s\n", _name);
printf("学生年龄:%d\n", _age);
}
private:
char _name[10];
int _age;
};
public下面到private都是公共域,外部使用者都可以直接访问里面的成员,private下面直到结束都是私有域,里面的成员禁止外部直接访问。
4.对象的储存
一个类中如果既有变量又有函数,那么当一个对象实例化的时候,它的大小怎么计算呢?
class A
{
public:
void test()
{
;
}
private:
int _a;
};
class B
{
private:
int _a;
};
class C
{
public:
void test()
{
;
}
};
class D{};
int main()
{
cout << sizeof(A) << endl;
cout << sizeof(B) << endl;
cout << sizeof(C) << endl;
cout << sizeof(D) << endl;
return 0;
}
我们可以分别计算各个类的大小,其中: A,B,C,D类大小分别为为4,4,1,1
那么我们可以得到结论,一个类的大小,实际就是该类中 ”成员变量” 之和,当然也要进行 内存对齐。空类比较特殊,编译器给了空类一个字节来唯一标识这个类,表明对象存在。
我们可以想象到,当一个类中有很多成员函数,并且多个对象同时存在时,不同对象中的成员变量不同,但是函数的实现是相同的,如果一个对象保存一份函数代码,相同代码保存多次,会造成浪费空间。
5.this指针
现在我们知道在多个对象中成员函数的代码只存在一份,那么怎么区分是哪个对象调用了函数呢?this出现解决了这个问题,this指针本质上其实是一个成员函数的形参,是对象调用成员函数时,将对象地址作为实参传递给this形参,而这操作是隐含的。
struct Student
{
public:
void getInfor()
{
printf("学生名字:%s\n", _name);
printf("学生年龄:%d\n", _age);
}
private:
char _name[10];
int _age;
};
int main()
{
Student st;
st.getInfor();
return 0;
}
this指针是成员函数第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要调用者传递。
其中this指针我们能直接使用:
struct Student
{
public:
void getInfor()
{
printf("学生名字:%s\n", this->_name);
printf("学生年龄:%d\n", this->_age);
}
private:
char _name[10];
int _age;
};
试想,一个对象的实例化做的是对它的变量进行开辟空间,而不同对象的地址都是不同的,也能调用其对象内的成员函数,那么空对象也能使用成员函数吗?
struct Student
{
public:
void getInfor()
{
printf("学生名字:%s\n", this->_name);
printf("学生年龄:%d\n", this->_age);
}
void test(){}
private:
char _name[10]={0};
int _age = 0;
};
int main()
{
Student* st = nullptr;
st->test();
st->getInfor();
return 0;
}
当我们一步一步运行的时候,发现st是可以调用test函数的,并且没有报错, 说明也能成员调用函数,但是当调用到getInfor()函数是就会报错,这是因为在成员函数getInfor()中访问了st对象中的数据,而st对象的地址为无效地址,所以会报错。
6.默认成员函数
6.1 构造函数
构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,保证每个数据成员都有 一个合适的初始值,并且在对象的生命周期内只调用一次。用来帮助对象初始化的成员函数。
struct Student
{
public:
Student()
{
_name[0] = '张';
_name[1] = '三';
_age = 18;
}
void Print()
{
printf("%s\n", _name);
printf("%d\n", _age);
}
private:
char _name[10];
int _age;
};
int main()
{
Student st1;
st1.Print();
return 0;
}
在st1创建的时候,会自动调用构造函数。其中,构造函数可以重载。
struct Student
{
public:
Student()
{
_name[0] = '张';
_name[1] = '三';
_age = 18;
}
Student(char* name, int age)
{
int count = 0;
while (name[count])
{
_name[count] = name[count];
count++;
}
_name[count] = name[count];
_age = age;
}
void Print()
{
printf("%s\n", _name);
printf("%d\n", _age);
}
private:
char _name[10];
int _age;
};
int main()
{
char a[] = "小黄";
Student st1(a,10);
st1.Print();
return 0;
}
使用构造函数有两种形式,有参数和无参数。 无参数:student st1; 有参数:Student st1(参数,参数); 非法形式:Student st1(); //和函数定义很像,所以这种形式是非法的。
6.2 构造函数的特性
- 函数名和和类名相同
- 对象实例化时编译器自动调用对应的构造函数
- 构造函数可以重载
- 无返回值
- 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成
6.3 默认构造函数
编译器自己生成的构造函数,无参的构造函数和全缺省的构造函数都是默认构造函数,默认函数如果存在,只能存在一个。
那么,编译器自己生成的默认构造函数都在干什么呢?
对于内置类型,不做处理,对于自定义类型如:class\struct 则调用它们的构造函数。
6.4 析构函数
析构函数也是特殊的成员函数,它的作用和构造函数相反,在对象的声明周期结束时,会自动调用析构函数来完成对象的一些资源清理工作。
定义: ~类名(){} 如:~Student(){}
struct Student
{
public:
Student()
{
_name[0] = '张';
_name[1] = '三';
_age = 18;
}
~Student()
{
cout << "调用析构函数" << endl;
}
private:
char _name[10];
int _age;
};
对于普通的局部变量,我们可以不用写析构函数,但是如果遇到需要我们手动释放堆中的资源,就是手动实现一个析构函数
6.5 析构函数的特性
- 无参数无返回值(无重载函数)
- 如果自己不实现会自动生成一个析构函数(自动生成的会去调用自定义类型的析构函数)
- 对象生命周期结束后自动调用
- 后定义的对象先调用析构函数
6.6 拷贝构造函数
在调用函数时的传参传对象时,会使用拷贝构造
定义: 类名(对象的引用){};
class Student
{
public:
Student()
{
_age = 18;
_name[0] = '张';
_name[1] = '三';
}
Student(const Student& stu)
{
_age = stu._age;
int count = 0;
while (stu._name[count])
{
_name[count] = stu._name[count];
count++;
}
_name[count] = stu._name[count];
}
private:
char _name[10];
int _age;
};
6.7 拷贝构造的特性
- 是构造函数的一个重载形式
- 拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用。(因为传值传参是调用拷贝构造函数的)
- 没有显示实现,会自动生成一个拷贝构造函数处理内置类型
通常来说,如果对象中的成员变量只有内置类型,而且没有在堆上开辟空间,那么编译器自己生成的拷贝构造就可以完成浅拷贝。
|