学习要慢慢学,模块化学习,不要怕,不要急,太着急学习,反而囫囵吞枣,学了一遍之后反而不剩下什么,还要重新学。
慢就是快,快就是慢。
1. 类的概念
类的概念,类可以把它理解成一个抽象的概念,比如,我们可以看到有金毛犬、萨摩耶犬、中华田园犬等等,很多很多的类型,那么可不可以将它们抽离出都具有的概念呢?对,它们都是犬,那么犬就是一个类。
class Dog{
};
既然有了犬这个类,犬都有哪些东西呢?有名字(人起的),有品种(萨摩耶、金毛),有四条腿,有一张嘴,有两个耳朵,有一个尾巴等等,这些可以观测到的,这些属于犬吧,这些都是静态的,是完完全全属于犬的,那么这就是犬的属性,也就是我们所说的成员变量。犬会做什么?犬会走,会汪汪叫,会握手,会吃饭等等,这些属于动态的,这就是犬的动作,也就说我们所说的成员函数。那么就很明确了。类是若干对象的类似属性的抽离,先有对象,才有类(也是归类)。对象有属性,有动作。正好对应编程里面的对象的成员变量和成员函数。
这里的 public 和 private 就是一个修饰符,public表示公开,private表示私有(类内才能访问)。
class Dog {
public:
void run();
void bark();
void eat();
private:
char *_name;
char *_type;
int _age;
};
然后呢,通过Dog这个类,创建各种各样的具体的犬。
Dog dog;
这里直接通过 Dog 这个类,定义了 dog 这个对象变量,此时的 dog 就具备了可访问的 run、bark、eat 方法。
此时,我们就理解了什么叫做类、什么叫做对象,什么叫成员变量,什么叫成员函数。
2. 构造函数
那我们怎么定义犬的名字、类型、年龄呢?总不能直接修改吧,(private 不能直接修改),所以可以添加一些函数,用于修改。
void setName(char *name);
void setAge(int age);
void setType(char *type);
添加一些函数,用于获取。
char *getName();
int getAge();
char *getType();
这里只给出了函数的定义,函数的实现留在末尾,前边主要对概念进行梳理。(关注抽象)
每一次定义一个对象,都通过 set,set,set去定义属性,是不是有些麻烦,每次都要多写3行,当然麻烦,能不能直接定义对象的时候就把属性给赋值了?当然可以!通过构造函数。
什么叫做构造函数?可以理解成定义对象的时候直接给好属性,按照这个属性去生成。就不用创建好胚子之后,再对胚子进行上色什么的。我直接让胚子在炉里就上好颜色再出炉。(当然,我不太了解这行业,只是打个比方)
构造函数的形式:
类名(){
}
对,构造函数的形式就是类名加一个括号,加大括号(写函数体)而且没有返回值,比如上面的 Dog,它的构造函数就是:
class Dog{
public:
Dog(){}
}
每个类都有构造函数,不管你写没写。那就出现疑问了,上面的明明没有写,却也能运行,创建出对象,为什么?
因为如果程序员没有手动写一个的话,系统会默认提供一个。当然,如果手动写了,那系统就不提供了。
构造函数因为有无参数也被分为有参和无参(这不是废话嘛),什么函数都是有参和无参。
构造函数,可以写很多很多,因为它满足函数重载。(没学过函数重载的,可以现在理解一下:函数重载,就是函数名相同,参数不同(参数类型不同、参数数量不同、参数位置不同)就会构成函数重载,只要满足这两项,它就是函数重载!别管那该死的返回值)
比如,我可以写成这样:
class Dog{
public:
Dog(){}
Dog(char* type, char* name, int age){}
Dog(char* type, char* name){}
Dog(char* type){}
Dog(char* name, int age){}
}
注意,如果写 Dog(char* type, char* name) 和 Dog(char* name, char* type) 这两个,是错误的,因为两个参数类型一样,会让计算机产生二义性(小子,你到底想走哪个?)
3. 初始化列表
初始化列表是真正的初始化,写在构造函数大括号里的语句,二者之间反而有些差别。如:
Dog(char* type, char* name, int age){
_type = new char[strlen(type) + 1]();
_name = new char[strlen(name) + 1]();
strcpy(_type, type);
strcpy(_name, name);
_age = age;
}
和
Dog(char* type, char* name, int age)
:_type(new char[strlen(type)+1]())
,_name(new char[strlen(name)+1]())
,_age(age){
strcpy(_type, type);
strcpy(_name,name);
}
下面这种,就是初始化列表,直接写在 () 和 {} 中间的语句,以 : 开头,以 ,进行分隔。
二者的区别:
- 使用初始化列表的,会初始化数据成员。
- 函数体内定义的版本,在构造函数体内对数据成员赋值。
还有一点,使用初始化列表的时候,初始化的顺序按照数据成员定义的顺序进行。如果顺序不太对的话,有出错的可能。
比如:
class Point{
Point(int x, int y)
:_x(x)
,_y(y){
}
int _x;
int _y;
}
上面这个是对的,那么如果把 _y(y) 改成 _y(_x) 也是对的。如果先写 _y(y) 再写 _x(_y) 就错了。此时没有按照定义顺序进行初始化。
Point(int x, int y)
:_y(y)
,_x(_y)
好了,这篇文章就讲这三点,学习要循序渐进,模块化的进行学习,不要怕,慢慢学,都能学的会。代码在下面,
代码:
#include <iostream>
using std::cout;
using std::endl;
class Dog {
public:
Dog() {
}
// 这里使用 const 是为了不让编译器报错。在C++里,纯字符串是 const char* 类型。
Dog(const char *type, const char *name, int age) {
_type = new char[strlen(type) + 1]();
_name = new char[strlen(name) + 1]();
_age = age;
}
void run() {
}
void bark() {
}
void eat() {
}
void setName(char *name) {
// strlen 计算一个字符串的字符数量(不包含结束字符),因此需要 +1
_name = new char[strlen(name) + 1]();
strcpy(_name, name);
}
void setAge(int age) {
_age = age;
}
void setType(char *type) {
_type = new char[strlen(type) + 1]();
strcpy(_type, type);
}
char *getName();
int getAge();
char *getType();
private:
char *_name;
char *_type;
int _age;
};
int main(int argc, char *argv[]) {
Dog dog_1;
Dog dog_2("萨摩耶", "茂茂", 3);
return 0;cc
}