从概念上来讲:类是现实中具备共同特色食物的抽象。程序员
这里举一个简单的例子:咱们如今须要定义的一我的的类。因此咱们要找出人和人的共同点(或者说人这个物种的共性),人有身高,体重,年龄,性别...这些就是人这个类的属性(成员变量)。人还会吃饭,走路,跳跃...这些就是人的方法(成员函数)。这样就能够抽象出人这个类。编程
当咱们抽象出人这个类以后咱们具体要怎么实现这个类呢?
请看以下语句数组
class Person { ... };
以上就是类 的基本语法:class 类的名字 {...}; 这里大括号最后有分号。
咱们在大括号里面写成员变量和成员方法。函数
class Person { public: int age;//人的年龄 void eat()//人吃饭的成员函数 { std::cout<<"人在吃饭"<<std::endl; } void run()//人跑步的成员函数 { std::cout<<"人在跑步"<<std::endl; } };
咱们也能够在类外写函数定义,类内只写函数声明。不过这时咱们须要用到做用域标识符(::)性能
class Person { public: int age;//人的年龄 bool sex;//人的性别 void eat();//人吃饭的成员函数 void run();//人跑步的成员函数 }; void Person::eat()//人吃饭的成员函数 { std::cout<<"人在吃饭"<<std::endl; } void Person::run() { std::cout<<"人在跑步"<<std::endl; }
在这里须要强调的是,定义位于类内的函数自动成为内联函数。若是但愿类外定义的函数也能够内联的话,须要手动加inline关键字。优化
这里和结构体很像Person xiaoming;
这里p就是Person的对象,他具备Person的属性和方法。简单的来讲,Person至关于人这个物种,而p这个对象就是具体到谁。
就像结构体那样:this
xiaoming.run(); xiaoming.eat();
在这里所建立的每个对象都有一个本身的存储空间,用于存储本身的成员变量和成员函数。可是多有对象都共享同一个类。debug
这里先简单的了解一下,以后会在继承中详细指出。(友元能够暂时无视)
private: 只能由该类中的函数、其友元函数访问,不能被任何其余访问,该类的对象也不能访问. 指针
protected: 能够被该类中的函数、子类的函数、以及其友元函数访问,但不能被该类的对象访问 code
public: 能够被该类中的函数、子类的函数、其友元函数访问,也能够由该类的对象访问
注意:这里要说明一个更好的方式来进行编程。把成员变量所有写进private中,来实现数据的封装。使用public的get set方法来得到和修改这个变量。
C++和Java不同,其中一个重要的区别就是:C++编译器强制要求程序员进行内存管理,而Java的内存管理是由JVM(Java虚拟机)代替程序员完成。因此,这里引出一个重要的概念(和C语言同样):内存管理对于C/C++开发者来讲是一个永恒的话题。
class Person { public: Person() { std::cout<<"我是构造函数"<<std::endl; } };
class Person { public: Person() { cout<<"默认构造函数"<<endl; } Person(int a) { age = a; cout<<"有参构造函数"<<endl; } };
通常状况,编译器会提供默认的构造函数,若是程序员手动写了有参的构造函数,那么编译器便不会提供默认的构造函数。
void test() { //构造函数调用方式 //括号法调用 Person p1(1); p1.age = 10; Person p2(p1); cout<<"p2年龄:"<<p2.Age<<endl; Person p3;//默认构造函数不加(); Person p3() 编译器任务这行是函数声明 //显示法调用 Person p4 = Person(100);//Person(100)叫匿名对象 Person p5 = Person(p4); Person(100);//若是编译器发现了匿名对象,那么在这行代码结束后,就释放这个对象 }
咱们能够经过以上方法对构造函数进行调用。
注意:上述语句中出现匿名对象。顾名思义,匿名对象就是没有名字,这种对象在这行代码结束后,就释放这个对象。(匿名对象未来会有伏笔,如今请记住!)
【补充】初始化列表:这里是有参构造函数的一种写法:
class foo { public: foo(string s, int i):name(s), id(i){} ; // 初始化列表 //相应的含义:把s的值赋值给name,把i的值赋值给id private: string name ; int id ; };
使用初始化列表的缘由,主要是由于性能。对于内置类型,如int, float等,使用初始化类表和在构造函数体内初始化差异不是很大,可是对于类类型来讲,最好使用初始化列表,使用初始化列表少了一次调用默认构造函数的过程,这对于数据密集型的类来讲,是很是高效的。因此,咱们能使用初始化列表的时候尽量使用初始化列表。
固然,有的时候咱们也必须使用初始化列表:
class Person { public: Person(const Person& p) { Age = p.Age; cout<<"拷贝构造函数"<<endl; } };
所谓拷贝构造函数就是把构造函数复制了一份
有关拷贝构造函数的调用时机:
class Person { public: Person() { cout<<"默认构造函数调用"<<endl; } Person(int a) { cout<<"有参构造函数调用"<<endl; } Person(const Person& p) { cout<<"拷贝构造函数调用"<<endl; } ~Person() { cout<<"析构函数调用"<<endl; } int Age; }; //用已经建立好的的对象初始化新的对象 void test() { Person p1 ; p1.Age = 10; Person(p1); } //用值传递的方式给函数参数传值 void doWork(Person p1) { } void test02() { Person p; p.Age; doWork(p); } //用值的方式返回局部对象 Person doWork2() { Person p1; return p1; } void test03() { Person p = doWork(); } //请注意debug模式下和release模式下的区别 //Release模式下的优化: /*(节省一份开销) Person p;//不调用默认构造 doWork2(p); void doWork2(Person &p) { Person p1;//调用默认构造 }
class Person { public: ~Person() { std::cout<<"我是析构函数"<<std::endl; } };
析构函数的做用其实是作垃圾回收的。具体的内容再后面的内存管理部分会详细说明。
这里只想说明一下编译器会为程序员默认的提供:无参构造函数,析构函数,拷贝构造函数,重载了赋值运算符(为知识完整性不得不说的,后面会细讲)
C++的数据和操做是分开存储的,而且每个非内联函数成员只会诞生出一份函数实例,即多人同类型的对象公用一块代码
这一块代码是如何区分是哪一个对象调用本身呢?
引入this指针,this指针是指向被调用的成员函数所属的对象
C++规定,this指针是隐含在对象成员函数内的一种指针。当对象被建立后,它的每个成员函数都含有一个系统自动生成的隐含指针this,用来保存这个对象的地址。虽然咱们 没有写上this指针,编译器在编译的时候会自动加上。this也称指向本对象的指针。this指针并非对象的一部分,也不会影响sizeof(对象)的结果。
this指针是C++实现封装的一种机制,他将对象和该对象调用的成员函数链接在一块儿。从外部看来,每个对象都拥有本身的成员函数。通常状况下,并不写this,而是让系统系进行默认配置。
this指针永远指向当前对象。this指针是一种隐含的指针,它隐含于每个非静态的成员函数中
class Person { public: Person(int age) { this->age = age;//编译器无法区分这几个age //区分有两种办法:1.换名字 2.this指针 } //对比年龄 void compareAge(Person &p) { if(this->age = p.age)//默认给加了this,不写也行 { cout<< "年龄相等"<<endl; } else { cout<<"年龄不等"<<endl; } } //年龄相加 Person& PlusAge(Person &p)//在这里若是去掉&,它将从一个引用传递变成值传递 { this->age += p.age; return *this;//指向本体 } int age; }
上面的语句不难理解,简单来讲就是为了区分名字。可是最后Person& PlusAge(Person &p)
可能不少人感受奇怪,这实际上是this指针的第二个用途——链式编程。
p1.PlusAge(p2).PlusAge(p2).PlusAge(p2);//链式编程 //若是变成值传递,每一次调用都是一个临时的空间里进行操做。并不会链式相加。这取决于你的需求
这种写法对于有Java基础的人来讲必定不陌生。
class Person { public: Person() { //构造中修改属性 //this永远指向本体 至关于 Person * const this //虽然指针不能修改,可是能够修改指针指向的值。 //若是我不但愿修改指针指向的值,则 const Person * const this this->A = 0; this->B = 0; } //常函数 void showInfo() const //不容许修改指针指向的值 { //this->C = 1000; // const Person * const this如何操做?,在函数()后面+const cout<<"A是"<<this->A<<endl; cout<<"B是"<<this->B<<endl; cout<<"C是"<<this->C<<endl; } int A; int B; //若有即使是常函数也要修改的需求,可使用关键字mutable mutable int C; }
固然,若是你真的理解了this指针的原理,则还能够以上这种C++风格的代码改写成C风格
//C++风格 void showInfo() const { ... } //C风格 void showInfo(cosnt Person * this) { ... }
若是这样改写的话,在调用时也要改写
//C++风格 p.showInfo(); //C风格 p.showInfo(&p);
有关对象数组,也是顾名思义,就是一个元素是对象的数组Person xiaoxue[4];
这里只有须要注意的地方:要建立的对象数组,则这个类必须有默认构造函数
若是但愿使用构造函数来初始化对象数组,则:
Person p[2] = { Person(10),//10岁 Person(12)//12岁 }; //也可使用不一样的构造函数 Person p[2] = { Person(10),//10岁 Person()//使用默认构造函数 };
咱们以前就知道全局做用域和局部做用域。在这里C++引入新的做用域——类做用域
类中定义的名称的做用域是整个类,做用域为整个类的名称只有在该类的做用域中是已知的,类外是不可知的。因此,不一样类使用相同的名字并不会冲突。
若是是类外的话,咱们可使用直接成员操做符(.),间接成员操做符(->),做用域操做符(::)来访问类中的public方法。前提是应该使用前包含类声明的头文件。
或许会有人这样去书写代码:
class Person { private: const int Len = 30; char Arr[Len]; };
这是不可行的,但确是一个常见的误区。
首先解释一下为何不可行:声明类只是描述类的形式,并无真正的建立对象。所以,在对象建立以前并无可使用的存储空间。
虽然上述的写法是错误的,可是咱们可使用别的方法达到咱们的目的。
class Person { private: enum{Len = 30};//使用枚举 char Arr[Len]; };
注意,这种方式声明枚举并不会建立数据成员。即全部对象并不包含枚举。并且Len仅仅是一个符号,当在做用域为整个类的代码中遇到它时,编译器将用30来替换。
至于第二种方法,咱们就要引入C++中一个很是重要的关键字 static。
在一个类中,若将一个成员变量声明为static,这种成员变量称为静态成员变量。与通常的成员变量不一样,不管创建多少个对象,都只有一个静态数据的拷贝。静态成员变量,属于某个类,对全部对象共享(其中一个修改了,其余的也都修改了)。
静态变量,在编译阶段就分配空间,对象尚未建立时,就已经分配空间
class Person { public: Person() { //Age = 10;//虽然不会报错,通常不会这么去作。 } static int Age;// 在类内声明,在类外定义。会报错! //静态成员变量也是有权限的 private: static int other;//私有权限,类内类外不能访问 }; int Person::Age = 0; int Person::other = 0;//仍然算类内访问
class Person { public: Person() { //Age = 10; } static int Age; //静态成员函数 //静态成员函数不能够访问普通的成员变量 //能够访问静态成员变量 static void func() { cout<<"func调用"<<endl; } //普通成员函数能够访问普通成员变量,也能够访问静态的成员变量 void MyFunc() { } private: static int other; //静态成员函数也有权限 static void func1() { cout<<"func1调用"<<endl; } }; int Person::Age = 0; int Person::other = 0;
在咱们了解了static关键字时,咱们就已经能够解决刚刚的问题了:
class Person { private: static const int Len = 30; char Arr[Len]; };
这里将建立一个静态的Len常量,这个常量将于其余的静态成员一块儿存储,而不是存储在对象中。(之后还会有机会遇到static)