文章目录
前言
此部分主要对C++类和对象的编程部分进行巩固。
参考资料源于:http://c.biancheng.net/cplus/
一.类的定义
1.类的定义
类是用户自定义的类型。一个类中包含成员变量和成员函数。
一个简单的类定义如下:
class Student{
public:
//成员变量
char* name;
int age;
//成员函数
void information(){
cout<<"姓名"<<name<<'\n'<<"年龄"<<age<<endl;
}
}; //注意此处有个分号,表示类定义结束
2.创建对象
定义完类后,可创建对象。可用点号’.'来访问成员变量和成员函数。
int main(){
Student students; //创建对象名为students
students.name = "Mr.Right";
students.age = 28;
students.information();
return 0;
}
3.使用对象指针
有两种方法,一种直接给对象指针赋值,一种给对象指针创建新对象。
//method 1
Student students;
Student* pstudents = &students;
//method 2
Student* pstudents = new Student;
通常使用method 2来创建新对象指针,此时需要用指针命令来访问类成员。
int main(){
Student* pstudents = new Student;
pstudents -> name = "Mr.Right";
pstudents -> age = 28;
pstudents -> information();
delete pstudents;
return 0;
}
4.成员函数在类外的定义
成员函数必须现在类内声明,可在类内或类外定义,在类内定义的成员函数默认为内联函数(inline),而在类外定义的不是内联函数,除非在外部定义时加上inline使其变成内联。
内联函数是将代码复制到调用的地方,能够减少因函数调用引起的额外开销(参数压栈、寄存器保存与恢复等),但也会增加代码大小。
一个合理的经验准则是,对函数体较小的进行内联,不要内联一个相当大的函数。
通常我们开发中采取的写法是将非内联函数放在类内声明,类外定义。将内联函数放在类内定义。
在外部定义的函数用域符号’::'来表示。
class Student{
public:
char* name;
int age;
//默认内联函数定义
void information(){
cout<<"姓名"<<name<<'\n'<<"年龄"<<age<<endl;
}
void information2(); //函数声明
};
void Student::information2(){
cout<<"姓名"<<name<<'\n'<<"年龄"<<age<<endl;
}
5.类成员的访问权限
符号 | 含义 |
---|---|
public | 可以被该类中的函数、子类的函数、友元函数访问,也可以由该类的对象访问 |
protected | 可以被该类中的函数、子类的函数、友元函数访问,但不可以由该类的对象访问 |
private | 可以被该类中的函数、友元函数访问,但不可以由子类的函数、该类的对象访问 |
成员变量大都以m_开头,这是约定成俗的写法,不是语法规定的内容。以m_开头既可以一眼看出这是成员变量,又可以和成员函数中的形参名字区分开。
二.类的成员函数
1.构造函数
构造函数是一种特殊的成员函数,它的名字和类名相同,没有返回值,不需要用户显式调用(也无法调用),而是在创建对象时强制自动执行。
class Student{
private:
char* m_name;
int m_age;
float m_score;
public:
Student(char* name, int age, float score); //构造函数
void information2(); //普通成员函数
};
//定义构造函数,实质为重载了构造函数,下篇会讲到
Student::Student(char* name, int age, float score){
m_name = name;
m_age = age;
m_score = score;
}
//定义普通成员函数
void Student::information2(){
cout<<"姓名"<<name<<'\n'<<"年龄"<<age<<endl;
}
2.构造函数的重载
定义一个类,会自动默认生成一个空构造函数,我们通常需要重载构造函数以达到我们的目的。
注意:我们一旦重载了构造函数,系统就不会自动生成空的构造函数,如果我们需要空构造函数,可以再重载一个。
以下代码源自:http://c.biancheng.net/view/2221.html,笔者稍加改动。
#include<iostream>
using namespace std;
class Student {
private:
char* m_name;
int m_age;
float m_score;
public:
Student(); //重载默认构造函数
Student(char* name, int age, float score); //重载默认构造函数
void information2(); //普通成员函数
void setname(char* name);
void setage(int age);
void setscore(float score);
};
//构造函数的重载
Student::Student() {
m_name = NULL;
m_age = 0;
m_score = 0.0;
}
Student::Student(char* name, int age, float score) {
m_name = name;
m_age = age;
m_score = score;
}
//定义普通成员函数
void Student::information2() {
if (m_name == NULL) {
cout << "error" << endl;
}
else {
cout << "姓名" << m_name << '\n' << "年龄" << m_age << '\n' << "分数" << m_score << endl;
}
}
void Student::setname(char* name) {
m_name = name;
}
void Student::setage(int age) {
m_age = age;
}
void Student::setscore(float score) {
m_score = score;
}
int main() {
//调用构造函数 Student(char *, int, float)
char name1[9] = "Mr.Right";
Student students(name1, 15, 92.5f);
students.information2();
//调用构造函数 Student()
Student* pstudents = new Student();
pstudents->information2();
pstudents->setname(name1);
pstudents->setage(16);
pstudents->setscore(96);
pstudents->information2();
return 0;
}
我们还可以用初始化列表给参数赋值,简洁明了。
variable_len_array::variable_len_array(int len):m_len(len){}
以上使用m_len(len)来给m_len赋值。
注意:
(1)初始化列表可以用于全部成员变量,也可以只用于部分成员变量。
(2)成员变量的初始化顺序与初始化列表中列出的变量的顺序无关,它只与成员变量在类中声明的顺序有关。
关于第二点,有如下解释:
class Demo{
private:
int m_a;
int m_b;
public:
Demo(int b);
};
Demo::Demo(int b): m_b(b), m_a(m_b){ }
实际上,运行结果是m_a变成随机值,m_b等于b。
理由如下:在初始化列表中,将m_b放在m_a前面,看似是先给m_b赋值,实际上赋值顺序是由他们在类中的声明顺序决定,因此仍然是先给m_a赋值,然而此时m_b还未被赋值,因此给m_a赋了一个奇怪的值。
3.析构函数
析构函数是一种特殊的成员函数,没有返回值,不需要程序员显式调用(无法显示调用),而是在销毁对象时自动执行。
析构函数的名字是在类名前加一个’~’。
析构函数没有参数,因此不能重载。
//模拟变长数组
class variable_len_array{
public:
variable_len_array(int len);
~variable_len_array(); //析构函数
private:
const int m_len; //数组长度
int* m_arr; //数组指针
};
variable_len_array::variable_len_array(int len):m_len(len){//使用初始化列表给m_len赋值
if(len > 0){
m_arr = new int[len]; //此处分配了内存,使用new创建的数据在程序关闭时不会自动删除,需要析构函数。
}
else{
m_arr = NULL;
}
}
variable_len_array::~variable_len_array(){
delete[] m_arr; //释放内存
}
三.静态与常数成员
1. this指针
类中的每一个函数都具有一个隐藏参数:this指针。
this指针是一个指针常量(指向的对象是哪个不能变,其内容可以变),指向当前对象。
variable_len_array::variable_len_array(int len):m_len(len){//使用初始化列表给m_len赋值
//参数列表实际为(*this, int len)
if(len > 0){
//这里的m_arr实际为this->m_arr
m_arr = new int[len];
}
else{
m_arr = NULL;
}
}
2.静态成员
(1)静态成员变量
静态成员变量实现了多个对象共享数据的目标。
静态成员变量属于类,而不属于某个具体的对象,用一个类创建的所有对象同时享用静态变量,即所有对象的静态变量保持相同。
class Student{
public:
Student(char* name, int age, float score);
void information();
static int m_total; //静态成员变量
private:
char* m_name;
int m_age;
float m_score;
};
注意,静态成员变量必须在类声明的外部初始化,初始化时不再加static关键字,需要有数据类型。
int Student::m_total = 0;
静态成员变量可以通过对象或类来访问。
//通过类类访问 static 成员变量
Student::m_total = 10;
//通过对象来访问 static 成员变量
char name[9] = "Mr.Right";
Student stu(name, 15, 92.5f);
stu.m_total = 20;
同理也可用动态对象的指针来访问。
(2)静态成员函数
静态成员函数可以直接通过类调用。
静态成员函数没有this指针,无法访问对象的普通成员,只能访问静态成员(包括变量和函数)。
class Student{
public:
Student(char *name, int age, float score);
public: //声明静态成员函数
static int getTotal();
static float getPoints();
private:
static int m_total; //总人数
static float m_points; //总成绩
private:
char *m_name;
int m_age;
float m_score;
};
//初始化静态成员变量
int Student::m_total = 0;
float Student::m_points = 0.0;
//定义静态成员函数(不加static),只能访问静态成员
int Student::getTotal(){
return m_total;
}
float Student::getPoints(){
return m_points;
}
int main(){
int total = Student::getTotal();
float points = Student::getPoints();
}
3.常成员
(1)常成员变量
和普通常量用法相同,声明时加上const。
但注意:
初始化常量成员变量的唯一方法是使用初始化列表。
class variable_len_array{
private:
const int m_len;
int *m_arr;
public:
variable_len_array(int len);
};
//由于m_len是常量成员变量,因此只能用初始化列表给其赋值
variable_len_array::variable_len_array(int len){
m_len = len;
m_arr = new int[len];
}
(2)常成员函数
函数定义和声明的时候(两个都要),在函数头部的结尾加上const。
//声明常成员函数
char* getname() const;
//定义常成员函数
char* Student::getname() const{
return m_name;
}
(3)常对象
const也可用来修饰对象。常对象只能调用类的常成员变量和函数。
四.总结
未完待续。