一、类
1.定义
对具有相似属性和行为的一组对象的统一描述
2.内容
数据成员,成员函数
3.定义形式
(1)用class
class 类名(首字母大写){
private:
私有的数据成员或成员函数
在类之外不能被访问,只有类中的成员函数可以访问private的数据成员和成员函数
定义类时,若未指明,默认为private
protected:
保护的数据成员或成员函数
只有类的成员函数及其子类(派生类)可以访问protected的成员
public:
公有的数据成员或成员函数
与外界的接口。外界只有通过这个接口才可以访问private的成员
};
此处:各成员函数实现的代码
(2)用struct
把上面代码的class换成struct即可,唯一区别是:未指明成员的访问属性时,默认为public
4.成员函数的定义
//1.在类中直接定义成员函数
//函数规模较小 ,一般为内联函数
class Book{
private:
char name[20];
int number;
public:
void regist(char *newName, int newNumber) {//成员函数regist()的定义
strcpy(name, newName);//给数据成员name赋值
number = newNumber;//给成员number赋值
}
void show() {
cout << "名称:"<<name<<endl;
cout << "号码:"<<number<<endl;
}
};
//2.在类中写声明,定义放在类后
//函数规模较大
//函数名前加上 类名::
class Book{
private:
char name[20];
int number;
public:
//成员函数regist()的声明
void regist(char *newName, int newNumber);
//成员函数show()的声明
void show();
};
//成员函数regist()的定义
void Book::regist(char *newName, int newNumber) {
strcpy(name, newName);//给数据成员name赋值
number = newNumber;//给成员number赋值
}
//成员函数show()的定义
void Book::show() {
cout << "名称:"<<name<<endl;
cout << "号码:"<<number<<endl;
}
5.编译器自动生成成员函数
若以下成员函数,用户没有为类实现,则编译器自动实现;若用户定义了,则编译器不提供默认实现
- 默认构造函数-空函数,什么也不做
- 析构函数-空函数,什么也不做(涉及指针的话就不对)
- 拷贝构造函数-按bit位复制对象所占内存的内容
- 移动构造函数-与默认拷贝构造函数一样
- 赋值运算符重载 -与默认拷贝构造函数一样
在默认函数定义或声明加上default,可显式生成默认函数
class T {
int data;
public:
T() = default;
T(int i) : data(i) {}
};
//T::T()=default;
6.类文件的管理
类的说明放在头文件(Book.h)中,看作类的外部借口;成员函数的定义放在源文件(Book.cpp)中,看作类的内部实现。
#include<Book.h>
void Book::regist(char *a, int b){}
二、对象
1.定义
对象是类的实例
2.定义格式
类名 对象名Book book1, book2;
book1是Book类型的变量,是Book类的对象
3.内容
每个对象有各自独立的存储单元;每个对象各自具有该类定义的一套数据成员(静态数据成员除外);成员函数是所有对象共有的,每个对象的函数成员都通过指针指向同一个代码空间
4.访问对象的成员
两种方法
void main {
//法1,对象.成员
Book book1, book2;
//调用成员函数regist,给book1的成员赋值
book1.regist("abc", 1001);
//调用成员函数regist,给book1的成员赋值
book2.regist("def", 1002);
//这里,成员函数如何知道某次调用的对象?其实regist除了接收两个实参外,还接收了一个对象book1的地址,被隐含的形参this所获取,即this = &book1,所有数据成员的访问都会加上this->,即
strcpy(this->name, newName);
this->number = newNumber;
//调用成员函数show,显示book1对象的数据成员的值
book1.show();
//法2
Book book3, *p = &book3;
p->show();
}
调用公有成员函数->向对象发送消息->对对象私有数据的访问
5.this指针
当参数与成员名称相同时,用this指针区别,this->的是成员
void Book::regist(char *name, int number) {
strcpy(this->name, name);//给数据成员name赋值
this->number = number;//给成员number赋值
}
6.作用域与生存期
(1)局部自动对象(包括函数的对象参数)
- 作用域是定义它的代码块或函数体
- 生存期从代码块的开始执行到代码块结束
- 每次执行代码块时重新构造对象
(2)静态对象(局部静态对象、全局对象)
- 作用域:定义它的函数体或程序文件
- 生存期:程序的某一次的整个运行期
- 构造静态对象的次序是按它们在程序中出现的先后次序,并在整个程序运行开始时(主函数运行前)只构造一次
7.初值
- 静态变量/对象:创建时自动赋初值0
- 自动变量/对象:创建时初值不确定
class Person{
private:
char name[20];
int age;
public:
void show() {
cout<<"Name:"<<name<<"|age:"<<age<<endl;
}
}person1;//定义静态对象
//静态对象:在所处模块初次运行时进行初始化工作,且只初始化一次
person1.show();//输出: |0
//自动对象:只有当定义它的函数被调用时才存在的对象
person person2;
person2.show(); //输出:烫烫烫|-8565634
8.初始化
//法1:只包含公有数据成员的类的对象可以这样初始化
//不建议用
class Date {
public:
int year, month, day;
};
today = {2008, 2, 10};
struct Date {
int year, month, day;
};
today = {2008, 2, 10};
//法2:利用构造函数
9.构造函数
(1)定义:构造函数是与类同名的成员函数,当创建对象时,构造函数被自动调用,可以用来对新对象进行初始化
(2)定义格式:类名(形参说明){函数体}
(3)注意
- 不能为构造函数指定返回值类型(包括void)
- 构造函数的定义可与其它函数成员一样,放在类说明的内部或外部均可
- 构造函数可有参,也可无参
- 每个类必须有一个构造函数
- 若类中未显式构造,则默认为一个函数体为空的无参函数,不做任何初始化操作;若显式构造了,则不再提供默认构造函数
- 在用默认构造函数创建对象时,若创建的是全局对象或静态对象,则对象的默认值为0,否则初始值不确定
- 构造函数和成员函数可以重载
#include<string.h>
#include<iostream.h>
class Person{
private:
char name[20];
int age;
int salary;
char tel[8];
public:
Person(char *xname, int xage, int xsalary, char *xtel);
void disp;
};
Person::Person(char *xname, int xage, int xsalary, char *xtel) {
strcpy(name, xname);//给name赋值
age = xage;
salary = xsalary;
strcpy(tel, xtel);
}
void Person::disp() {
cout<<endl;
cout<<"姓名:"<<name<<endl;
cout<<"年龄:"<<age<<endl;
cout<<"工资:"<<salary<<endl;
cout<<"电话:"<<tel<<endl;
}
void main() {
Person mrzhang("张三", 25, 850, "12345678");
mrzhang.disp();
}
(4)构造函数重载的例子:可见其匹配方式与普通函数重载的相同
#include <string.h>
#include <iostream>
using namespace std;
//构造函数的重载
class Book{
private:
char name[20];
int number;
public:
Book();//第一个构造函数说明
Book(char *newName, int number);//第二个构造函数说明
void show();
};
Book::Book() {
strcpy(name, "no name");
number = 0;
}
Book::Book(char *newName, int newNumber) {
strcpy(name, newName);
number = newNumber;
}
void Book::show() {
cout << "名称:"<<name<<endl;
cout << "号码:"<<number<<endl;
}
int main() {
//自动调用第二个构造函数
Book mybook("Visual C++6.0", 10020);
mybook.show();
//自动调用第一个构造函数
Book yourbook;
yourbook.show();
return 0;
}
(5)当构造函数有默认参数时,称为具有默认参数的构造函数,使用时要防止二义性
class Myclass{
private:
int number;
public:
Myclass();
Myclass(int i = 10);
};
Myclass::Myclass() {
member = 10;
}
Myclass::Myclass(int i) {
member = i;
}
void main() {
Myclass x(20);
Myclass y;//此行产生二义性,无法确定调用哪个构造函数
}
(6)构造函数的初始化表:可以在表中为数据成员赋初值
格式:类名(形参表):数据成员1(初值1),...
初值可以是常量或形参表中的参数
class Date{
public:
Date():year(2000), month(3), day(1) { }
Date(int y, int m, int d):year(y), month(m), day(d) { }
private:
int year, month, day;
};
(7)在构造函数的初始化表中,还可以调用其他构造函数,成为“委派构造函数”
class Info {
public:
Info() { Init(); }
Info(int i) : Info() { id = i; }
Info(char c): Info() { gender = c; }
private:
void Init() { ... }//其他初始化
int id {2016};
char gender {'M'};
};
10.拷贝
(1)通过对象赋值实现拷贝
//同一个类生成的两个对象之间赋值,数据成员会赋值过去
#include <Book.h>
void main {
Book book1, book2;
book1.regist("abc", 1001);
book2 = book1;
book1.show(); //"abc", 1001
book2.show(); //"abc", 1001
}
(2)拷贝构造函数
定义格式:类名([const]类名 &形参) {函数体};
说明:
- 拷贝构造函数名与类名相同,只有一个参数,该参数就是对一个该类对象的引用
- 功能:通过将一个同类型对象的值拷贝给另一个新对象,来完成对新对象的初始化
#include <string.h>
#include <iostream>
using namespace std;
class Book{
private:
char name[20];
int number;
public:
//成员函数regist()的声明
void regist(char *newName, int newNumber);
//成员函数show()的声明
void show();
};
//成员函数regist()的定义
void Book::regist(char *newName, int newNumber) {
strcpy(name, newName);//给数据成员name赋值
number = newNumber;//给成员number赋值
}
//成员函数show()的定义
void Book::show() {
cout << "名称:"<<name<<endl;
cout << "号码:"<<number<<endl;
}
//以上可以写成#include<Book.h>
//拷贝构造函数
class Point {
private:
int x, y;
public:
Point() { }
Point(int inx, int iny) {
x = inx;
y = iny;
}
Point(Point &p) {
x = p.x;
y = p.y;
cout<<"执行拷贝构造函数"<<endl;
}
void show(char *name) {
cout<<endl;
cout<<name
<<"("<<x
<<","<<y<<")"
<<endl;
}
};
void Print(Point p) {
p.show("");
}
int main() {
Point p1(1, 2), p2;
p2 = p1;
cout<<"执行对象间的赋值"<<endl;
Point p3(p1);//执行拷贝构造函数
p1.show("p1");//p1(1,2)
p1.show("p2");//p2(1,2)
p1.show("p3");//p3(1,2)
Point p4 = p1;
cout<<endl<<"p4";//p4
Print(p4);//执行拷贝构造函数(1,2)
return 0;
}
若未提供显式拷贝构造函数,则系统自动提供默认拷贝构造函数,功能是把作为参数的对象的数据成员逐个拷贝到目标对象中,称为成员级复制(浅拷贝)
#include <string.h>
#include <iostream>
using namespace std;
class Person {
private:
char *name;
int num;
public:
Person(int i, char *str) {//浅拷贝函数
name = new char[strlen(str) + 1];
strcpy(name, str);
num = i;
}
void show() {
cout<<"name = " << name <<endl;
cout<<"num = " << num <<endl;
}
void SetName(char *str) {
strcpy(name, str);
}
void SetNum(int n) {
num = n;
}
};
int main() {
//调用函数Person(int i, char *str)
Person person1(215, "a");
//使用拷贝构造函数构造person2(默认的)
Person person2(person1);
//更新person2的name
person2.SetName("b");
person2.SetNum(216);
cout<<"Person1:"<<endl;
person1.show();
cout<<"Person2:"<<endl;
person2.show();
//输出:
//Person1:
//name = b
//num = 215
//Person2:
//name = b
//num = 216
}
深拷贝
public:
Person(const Person &x) {//深拷贝函数
name = new char[strlen(x.name) + 1];
strcpy(name, x.name);
num = x.num;
}
//输出:
//Person1:
//name = a
//num = 215
//Person2:
//name = b
//num = 216
11.移动构造函数(*)
(1)语法:ClassName(ClassName&&);
(2)目的:
偷临时变量中的资源(如内存)
临时变量被编译器设置为常量形式,使用拷贝构造函数无法将资源偷出来
12.析构函数
(1)定义格式:~类名(){函数体};
(2)特点:是类的特殊成员函数,一个类只能有一个析构函数,不能重载;无返回值,无参数,在对象生命期结束时,被系统自动调用
(3)功能:当对象使用完毕后,占用的系统资源需要在对象消失前被释放
(4)默认析构函数:未提供时提供默认的,~类名(){}。一般用默认的就行,但如果在类的对象中有动态分配的内存(如用new申请分配的内存)时,必须为该类提供析构函数。
#include <string.h>
#include <iostream>
using namespace std;
class Teacher {
private:
char *name;
int age;
public:
Teacher(char *newName, int newAge) {
//用new为name对象分配堆内存
name = new char[strlen(newName) +1];
strcpy(name, newName);
age = newAge;
cout<<"执行构造函数Teacher()"<<endl;
};
~Teacher() {
delete name;
cout<<"执行析构函数~Teacher()"<<endl;
} ;
void show();
};
void Teacher::show() {
cout<<"姓名:"<<name<<endl<<"年龄:"<<age<<endl;
}
int main() {
Teacher teacher("a",25);
teacher.show();
cout<<endl;
return 0;
}
//输出
//执行构造函数Teacher()
//姓名:a
//年龄:25
//执行析构函数~Teacher() 啥时候执行的?
13.堆对象
- 用new和delete乐意动态分配或释放堆内存
- 可以用new建立对象(会自动调用构造函数)
- 利用delete可删除对象(会自动调用析构函数)
- 堆对象一旦创建就一直存在,直到程序运行结束,可能被自动销毁,或用delete释放
#include <string.h>
#include <iostream>
using namespace std;
class TDate {
public:
TDate(int m, int d, int y);
void show();
private:
int month, day ,year;
};
TDate::TDate(int m, int d, int y) {
month = m; day = d; year = y;
}
void TDate::show() {
cout<<month<<"/"
<<day<<"/"
<<year<<endl;
}
int main() {
TDate *pd;
pd = new TDate(1, 1, 1998);
pd->show();
delete pd;
return 0;
}
14.对象数组
(1)定义:一个数组的类型除了可以为基本都数据类型外,还可以为类类型,则该数组中的每个元素都是该类的一个对象
(2)定义格式:类名 数组名[数组大小]
//第一种方法
#include <string.h>
#include <iostream>
using namespace std;
class Person {
private:
char *name;
int age;
public:
Person();
~Person();
void assignment(char *a, int b);
void show();
};
Person::Person() {
name = new char('\0');
age = -1;
}
Person::~Person() {
delete[] name;
}
void Person::assignment(char *a, int b) {
name = new char[strlen(a) + 1];
strcpy(name, a);
age = b;
};
void Person::show() {
cout<<"\n 姓名:"<<name<<"年龄:"<<age;
};
int main() {
//定义对象数组
Person employee[5];
//赋值
employee[0].assignment("a", 25);
employee[1].assignment("b", 25);
employee[2].assignment("c", 25);
employee[3].assignment("d", 25);
employee[4].assignment("e", 25);
int i;
for(i = 0; i < 5; i++) {
employee[i].show();
}
return 0;
return 0;
}
//第二种初始化数组方法
class Person {
private:
char *name;
int age;
public:
Person();
~Person();
Person(char *a, int b);
void show();
};
Person::Person() {
name = new char('\0');
age = -1;
}
Person::~Person() {
delete[] name;
}
Person::Person(char *a, int b) {
name = new char[strlen(a) + 1];
strcpy(name, a);
age = b;
};
void Person::show() {
cout<<"\n 姓名:"<<name<<"年龄:"<<age;
};
int main() {
Person employee[5] = {
Person("a", 25),
Person("b", 25),
Person("c", 25),
Person("d", 25),
Person("e", 25)
} ;
int i;
for(i = 0; i < 5; i++) {
employee[i].show();
}
return 0;
}
15.类作用域
(1)定义:在类定义包括的范围
(2)小于文件域,大于函数域
(3)在类域内定义的变量不能使用auto、register和extern等修饰符
(4)在类域内定义的静态数据成员和静态成员函数具有外部的链接属性
#include <string.h>
#include <iostream>
using namespace std;
class Myclass {
private:
int x;
int y;
public:
Myclass(int a, int b) {
x = a; y = b;
}
void print();
void myfunc();
};
void Myclass::print() {
cout<<"x="<<x<<","<<"y="<<y<<endl;
}
//①类中的成员具有类作用域,在成员函数中可以直接引用类的数据成员。但如果在成员函数中定义了同名的局部变量,就要用::区分
//②在类的定义外,由于不同的成员函数可以具有相同的名字,因此需要用::指明该成员函数所属的类
void Myclass::myfunc() {
int x = 9, y = 10;
//输出局部变量
cout<<"In myfunc:x ="<<x<<","<<"y="<<y<<endl;
//输出类的数据成员
cout<<"Myclass::x="<<Myclass::x <<","<<"Myclass::y="<<Myclass::y<<endl;
}
int main() {
//③在类外访问类的成员时,必须通过对象名或指向对象的指针。对象名用.,指针用->
Myclass test(100, 200), *ptest = &test;
test.print();//通过对象名访问公有成员
ptest->myfunc();//通过指向对象的指针访问共有成员
}
16.对象成员
(1)定义:一个类的对象作为另一个类的数据成员
(2)定义格式:class x { 类名1 对象1;...其他对象成员};
(3)这是类之间的包含关系has-a
(4)初始化:如果类A的对象是类B的数据成员,那么在创建类B的对象时,类A的对象也会被自动创建
(5)包含对象成员的对象的初始化:在构造类B的对象时,首先调用类A的构造函数,初始化对象成员,再执行类B自己的构造函数,初始化类中的非对象成员;对于同一类中的多个对象成员,按类中说明顺序调用相应构造函数进行初始化
(6)若类A的构造函数为有参,则必须在B的初始化表中指明A的构造函数和参数。参数表1提供初始化成员1所需参数,这几个参数表中的参数均来自参数表0,初始化B的非对象成员所需的参数吗,也来自参数表0
B::B(参数表0):成员1(参数表1),成员2(参数表2),...
#include <string.h>
#include <iostream>
using namespace std;
class Student {
public:
Student() {
cout<<"构造函数Student()"<<endl;
}
};
class Teacher {
public:
Teacher() {
cout<<"构造函数Teacher()"<<endl;
}
};
class Tourpair {
public:
Tourpair() {
cout<<"构造函数Tourpair()"<<endl;
}
protected:
Student student;
Teacher teacher;
};
int main() {
Tourpair tp;
cout<<"回到main函数"<<endl;
return 0;
}
//输出:
// 构造函数Student()
// 构造函数Teacher()
// 构造函数Tourpair()
// 回到main函数
例:对象成员的初始化
#include <string.h>
#include <iostream>
using namespace std;
class Student {
protected:
char name[40];
public:
Student(char *pName = "Noname") {
cout<<"构造函数Student:"<<pName<<endl;
strncpy(name, pName, sizeof(name));
//strncpy(char *dest, const char *src, size_t n) 把 src 所指向的字符串复制到 dest,最多复制 n 个字符。当 src 的长度小于 n 时,dest 的剩余部分将用空字节填充。
name[sizeof(name-1)] = '\0';
}
Student(Student &s) {
cout<<"拷贝构造Student:"<<s.name<<"的克隆"<<endl;
strcpy(name, s.name);
strcat(name, "的克隆");
}
~Student() {
cout<<"析构Student:"<<name<<endl;
}
};
class Tutor {
protected:
Student student;
public:
Tutor(Student &s) :student(s) {//这是初始化表,调用student的拷贝构造函数
cout<<"构造函数Tutor()"<<endl;
}
~Tutor() {
cout<<"析构Tutor:"<<endl;
}
};
int main() {
Student st1;//此处调用Student的构造函数 Student(char *pName = "Noname")
Student st2("LiHai");//同上
// 此处调用Tutor的构造函数 Tutor(Student &s)
//在构造tutor对象的过程中,用初始化表调用
//Student类的拷贝构造函数Student(Student &s)
Tutor tutor(st2);
return 0;
}
//输出
//构造函数Student:Noname
//构造函数Student:LiHai
//拷贝构造Student:LiHai的克隆
//构造函数Tutor()
//析构Tutor:
//析构Student:LiHai的克隆
//析构Student:LiHai
//析构Student:Noname
17.常量数据成员的初始化
对象的数据成员初始化——用构造函数。但如果是常量成员或引用成员,就不行了,只能用初始化表。
class Sillyclass {
//该构造函数初始化方法不正确
// public:
// sillyclass(int i) {
// ten = 10;
// refi = 1;
// }
//下面是对的初始化方式
public:
sillyclass(int i):ten(10), refi(i) {}
protected:
const int ten;//常量数据成员
int &refi;//引用成员
};
18.常成员函数
声明和定义格式:
class 类名 {
类型 函数名{参数表} const; //const可以参与区分重载函数
};
类名::函数名{参数表} const {
}
- 为只读函数,可以读取数据成员的值,但不能修改
- 不能调用类中其他非常成员函数
19.常对象(对象常量)
定义格式:const 类名 对象名; 类名 const 对象名;
class X {
private:
int m;
public:
void SetM(int inVal);
int GetM();
};
void X::SetM(int inVal) {
m = inVal;
}
int X::GetM() {
return m;
}
void main() {
const X obj;//警告
//不能调用常对象的非const型的成员函数
obj.SetM(10);//报错
obj.GetM();//报错
}
20.静态成员
静态成员包括静态数据成员和静态成员函数,是同一个类的所有对象公有的
(1)静态数据成员
- 定义
static 类型说明符 成员名
,例如在public中static int count;
- 赋初值语句写在全局中,必须指明类,在类外
int Visitor::count = 0;
- 在一个类的对象空间中,不包含静态成员的空间,所以静态成员空间不会随着对象产生和消失而变化
- 静态数据成员的空间在程序开始运行时就被分配
- 对于在类的public中说明的静态数据成员,可以不使用成员函数而直接访问,即使未定义类的对象也可以直接访问,但要指明所属的类,如在main中
cout<<Visitor::count;
- private和protected中的静态成员只能通过类的成员函数访问
#include <iostream>
#include <string.h>
using namespace std;
class Visitor {
private:
char *name;
int serial_number;
public:
static int count;
void Regist(char *a);
};
int Visitor::count = 0;//静态变量赋初值
void Visitor::Regist(char *a) {
name = new char[strlen(a)+1];
strcpy(name, a);
serial_number = ++count;
cout<<"编号:"<<serial_number<<endl;
}
int main() {
Visitor visitor[3];//定义对象数组
int i;
char str[8];
cout<<endl;
for(i = 0; i < 3; i++) {
cout<<"输入姓名:";
cin>>str;
visitor[i].Regist(str);
}
cout<<endl;
cout<<Visitor::count;
return 0;
}
(2)静态成员函数
- 定义:static 类型 函数名(形参){函数体};
- 访问静态成员函数时,可以不需要对象
- 静态成员函数不能访问非静态成员
#include <iostream>
using namespace std;
class objcount {
private:
static int count;
public:
objcount() {
count++;
}
static int get() {
return count;
}
};
int objcount::count = 3;//静态变量赋初值
int main() {
cout<<objcount::get()<<endl;
objcount a, b, c, d, e, f;
//静态成员函数可以通过类名引用
cout<<objcount::get()<<endl;
//也可以通过对象名引用
cout<<a.get()<<endl;
}
21.友元
(1)友元
- 不是类的成员函数,能访问一个或多个类的所有成员
- 定义:类内或类外
- 声明:类内,需要在各个类中分别声明,
friend 名称
(2)友元函数
- 声明:
friend 函数类型 函数名(类名 参数);
- 一个类的友元函数与成员函数一样,有对该类成员的访问权
- 不允许将某个类的构造函数、析构函数、虚函数声明为友元函数
- 可以把另一个类的公有成员函数声明为自己的友元函数
(3)友元类
- 若类A被说明为类B的友元类,则类B的成员就可以被类A的成员访问
- 定义:在classB中写
friend class A;
- 友元类的所有成员函数都可视为该类的友元函数,能存取该类的私有成员和保护成员
- 没有对称性和传递性
22.子对象
子对象的初始化在构造函数的成员初始化列表中进行
class C3 {
int num;
C1 obj1;
C2 obj2;
};
调用构造函数:C1->C2->C3
调用析构函数:C3->C2->C1