既然找不到实习那就开始学习C++吧!?Day01-Day02
1.类和对象
- 把变量(属性)和函数(操作)合成一个整体,封装在一个类中
- 对变量和函数进行访问控制
访问权限
- 在类的内部(作用域范围内),没有访问权限之分,所有成员可以相互访问
- 在类的外部(作用域范围外),访问权限才有意义:public,private,protected
- 在类的外部,只有public修饰的成员才能被访问,在没有涉及继承与派生时, private和protected是同等级的,外部不允许访问
访问属性 | 属性 | 对象内部 | 对象外部 |
---|---|---|---|
public | 公有 | 可访问 | 可访问 |
private | 私有 | 可访问 | 不可访问 |
protected | 泡壶 | 可访问 | 不可访问 |
class helloworld
{
private:
int age;
public:
int height;
protected:
int weight;
public:
void sayhello();
};
void helloworld::sayhello()
{
cout << "hello world" << endl;
}
int main(){
helloworld h;
h.height = 10;
h.sayhello();
return EXIT_SUCCESS;
}
2.构造函数和析构函数
1.构造函数
构造函数函数名和类名相同,没有返回值,不能有void,但可以有参数。如:
ClassName(){}
2.析构函数
析构函数函数名是在类名前面加”~”组成,没有返回值,不能有void,不能有参数,不能重载。如:
~ClassName(){}
class Person
{
public:
char* name;
int age;
int height;
public:
Person(/* args */);
~Person
();
};
Person::Person(/* args */)
{
cout << "调用构造函数" << endl;
name = (char*)malloc(sizeof("Jack"));
strcpy(name, "Jack");
age = 10;
height = 180;
}
Person::~Person()
{
cout << "析构函数调用!" << endl;
if (name != NULL){
free(name);
name = NULL;
}
}
3.构造函数的分类
-
按参数类型:分为无参构造函数和有参构造函数
-
按类型分类:普通构造函数和拷贝构造函数(复制构造函数)
class Person
{
public:
int mage;
int mheight;
public:
Person(/* args */);
Person(int age, int height);
Person(const Person& Person);
};
Person::Person(/* args */)
{
cout << "调用无参构造函数" << endl;
mage = 10;
mheight = 180;
}
Person::Person(int age, int height)
{
cout << "调用有参构造函数" << endl;
mage = age;
mheight = height;
}
Person::Person(const Person &Person)
{
cout << "调用拷贝构造函数" << endl;
mage = Person.mage;
mheight = Person.mheight;
}
int main(){
Person p1;
cout << p1.mage << p1.mheight << endl;
Person p2(11,150);
cout << p2.mage << p2.mheight << endl;
Person p3(p2);
cout << p3.mage << p3.mheight << endl;
return EXIT_SUCCESS;
}
4.拷贝构造函数的调用时机
-
对象以值传递的方式传给函数参数
-
函数局部对象以值传递的方式从函数返回(vs debug模式下调用一次拷贝构造,qt不调用任何构造)
-
用一个对象初始化另一个对象
class Person{
public:
Person(){
cout << "no param contructor!" << endl;
mAge = 10;
}
Person(int age){
cout << "param constructor!" << endl;
mAge = age;
}
Person(const Person& person){
cout << "copy constructor!" << endl;
mAge = person.mAge;
}
~Person(){
cout << "destructor!" << endl;
}
public:
int mAge;
};
//1. 旧对象初始化新对象
void test01(){
Person p(10);
Person p1(p);
Person p2 = Person(p);
Person p3 = p; // 相当于Person p2 = Person(p);
}
//2. 传递的参数是普通对象,函数参数也是普通对象,传递将会调用拷贝构造
void doBussiness(Person p){}
void test02(){
Person p(10);
doBussiness(p);
}
//3. 函数返回局部对象
Person MyBusiness(){
Person p(10);
cout << "局部p:" << (int*)&p << endl;
return p;
}
void test03(){
//vs release、qt下没有调用拷贝构造函数
//vs debug下调用一次拷贝构造函数
Person p = MyBusiness();
cout << "局部p:" << (int*)&p << endl;
}
5.构造函数的调用规则
默认情况下C++至少为我们写的类增加三个构造函数
- 默认构造函数(无参,函数体为空)
- 默认析构函数(无参,函数体为空)
- 默认拷贝构造函数,对类中非静态成员属性简单值拷贝
如果用户定义拷贝构造函数,c++不会再提供任何默认构造函数
如果用户定义了普通构造(非拷贝),c++不在提供默认无参构造,但是会提供默认拷贝构造
6.深拷贝与浅拷贝
浅拷贝:同一类型的对象之间可以赋值,使得两个对象的成员变量的值相同,两个对象仍然是独立的两个对象,这种情况被称为浅拷贝.
一般情况下,浅拷贝没有任何副作用,但是当类中有指针,并且指针指向动态分配的内存空间,析构函数做了动态内存释放的处理,会导致内存问题。
**深拷贝:**当类中有指针,并且此指针有动态分配空间,析构函数做了释放处理,往往需要自定义拷贝构造函数,自行给指针动态分配空间,深拷贝。
Person(const Person& person){
pName = (char*)malloc(strlen(person.pName) + 1);
strcpy(pName, person.pName);
mAge = person.mAge;
}
7.初始化列表
class Person
{
public:
int mage;
int mheight;
public:
Person(/* args */);
Person(int age, int height):mage(age),mheight(height){}
Person(const Person& Person);
};
Person::Person(/* args */)
{
cout << "调用无参构造函数" << endl;
mage = 10;
mheight = 180;
}
Person::Person(const Person &Person)
{
cout << "调用拷贝构造函数" << endl;
mage = Person.mage;
mheight = Person.mheight;
}
int main(){
Person p1;
cout << p1.mage << p1.mheight << endl;
Person p2(11,150);
cout << p2.mage << p2.mheight << endl;
Person p3(p2);
cout << p3.mage << p3.mheight << endl;
return EXIT_SUCCESS;
}
- 类对象作为成员时,使用初始化列表正确调用成员类对象的构造函数。
- 当调用构造函数时,首先调用类对象成员的构造函数,对这些对象初始化,最后在调用函数本身。
- 也就是说先调用对象成员的构造函数,再调用本身的构造函数
class Person
{
public:
int mage;
int mheight;
public:
Person(/* args */);
Person(int age, int height);
Person(const Person& Person);
};
Person::Person(/* args */)
{
cout << "调用无参构造函数" << endl;
mage = 10;
mheight = 180;
}
Person::Person(int age, int height)
{
mage = age;
mheight = height;
}
Person::Person(const Person &Person)
{
cout << "调用拷贝构造函数" << endl;
mage = Person.mage;
mheight = Person.mheight;
}
class man
{
private:
string mname;
public:
man(/* args */);
man(string name);
~man();
};
man::man(string name)
{
mname = name;
cout << "what can I say, " <<name <<"!?"<<endl;
}
man::man(/* args */)
{
cout << "what can I say, man!?" << endl;
}
man::~man()
{
}
class student
{
private:
int mID;
man M;
Person p;
public:
student(/* args */);
student(int ID,int age, int height, string name):p(age,height),M(name),mID(ID){
cout << "调用构造函数" << endl;
cout << mID << " " << age << " " << name << " " << endl;
}
~student();
};
student::student(/* args */)
{
}
student::~student()
{
}
int main(){
student s(1,12,123,"jack");
return EXIT_SUCCESS;
}
8.explicit
用于禁止构造函数的隐式转换。
(针对于单参数的构造函数,或者除了第一个参数其他参数都有默认值的构造函数)
3.动态对象的创建
根据需求分配空间
1.对象创建的过程
- 为对象分配内存
- 使用对象的构造函数初始化那一部分的内存(需要程序员确保)
2.使用new初始化对象
- C++中解决动态内存分配的方案是把创建一个对象所需要的操作都结合在一个称为new的运算符里。当用new创建一个对象时,它就在堆里为对象分配内存并调用构造函数完成初始化。
- New操作符能确定在调用构造函数初始化之前内存分配是成功的,所有不用显式确定调用是否成功。
- new返回一个指针。
int main(){
student* s = new student(1,12,123,"jack");
return EXIT_SUCCESS;
}
3.使用delete删除对象
- delete是new的反向表达式。delete先调用对象的析构函数,然后释放内存。
- delete需要传入一个对象的地址,只是用于new创建的对象。
4.用于数组的new和delete
-
使用new和delete在堆上创建数组非常容易。
//创建字符数组 char* pStr = new char[100]; //创建整型数组 int* pArr1 = new int[100]; //创建整型数组并初始化 int* pArr2 = new int[10]{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; //释放数组内存 delete[] pStr; delete[] pArr1; delete[] pArr2;
-
当创建一个对象数组时,必须对数组中的每一个对象都调用构造函数。
-
除了在栈上可以聚合初始化,必须提供一个默认的构造函数。
class Person{ public: Person(){ pName = (char*)malloc(strlen("undefined") + 1); strcpy(pName, "undefined"); mAge = 0; } Person(char* name, int age){ pName = (char*)malloc(sizeof(name)); strcpy(pName, name); mAge = age; } ~Person(){ if (pName != NULL){ delete pName; } } public: char* pName; int mAge; }; void test(){ //栈聚合初始化 Person person[] = { Person("john", 20), Person("Smith", 22) }; cout << person[1].pName << endl; //创建堆上对象数组必须提供构造函数 Person* workers = new Person[20]; }
-
使用new[]对对象数组初始化时也要用delete[]删除
new了十个对象,只delete了一个 如果在new表达式中使用[],必须在相应的delete表达式中也使用[].如果在new表达式中不使用[], 一定不要在相应的delete表达式中使用[].
- new了十个对象,只delete了一个
- 如果在new表达式中使用[],必须在相应的delete表达式中也使用[].如果在new表达式中不使用[], 一定不要在相应的delete表达式中使用[].
5.delete void*可能会出错
-
如果对一个void*指针执行delete操作,这将可能成为一个程序错误,除非指针指向的内容是非常简单的,因为它将不执行析构函数.以下代码未调用析构函数,导致可用内存减少。
class Person{ public: Person(char* name, int age){ pName = (char*)malloc(sizeof(name)); strcpy(pName,name); mAge = age; } ~Person(){ if (pName != NULL){ delete pName; } } public: char* pName; int mAge; }; void test(){ void* person = new Person("john",20); delete person; }
4.静态成员
在类定义中,它的成员可以用static声明为静态的,称为静态成员。
对于静态成员,无论声明了多少对象,都只有一个拷贝,所有对象共用这一个拷贝。
1.静态成员变量
- 静态成员变量必须在类中声明,在类外定义。
- 静态成员变量不属于某个对象,在为对象分配空间中不包括静态成员所占空间。
- 静态成员变量可以通过类名或者对象名来引用。(没创建前即可用类名调用)
- 成静态成员变量也有访问权限
class Person
{
private:
static int mage;
public:
static int mheight;
public:
Person(/* args */);
~Person();
};
int Person::mheight = 10;
int Person::mage = 10;
int main(){
Person::mheight = 12;
cout << "Person::mheight:" << Person::mheight << endl;
Person p1,p2;
p1.mheight = 22;
//所有对象共用一个拷贝
cout << "Person::mheight:" << p1.mheight << endl;
cout << "Person::mheight:" << p2.mheight << endl;
return EXIT_SUCCESS;
}
2.静态成员函数
- 有static说明的成员函数被称为静态成员函数
- 对象没有创建前可用类名调用
- 主要用于访问静态成员变量,但是不能访问普通成员变量
- 静态成员函数只能访问静态变量,不能访问普通成员变量;普通成员函数可访问静态成员变量、也可以访问非静态成员变量
- 静态成员函数的使用和静态成员变量一样
- 静态成员函数也有访问权限
class Person{
public:
//普通成员函数可以访问static和non-static成员属性
void changeParam1(int param){
mParam = param;
sNum = param;
}
//静态成员函数只能访问static成员属性
static void changeParam2(int param){
//mParam = param; //无法访问
sNum = param;
}
private:
static void changeParam3(int param){
//mParam = param; //无法访问
sNum = param;
}
public:
int mParam;
static int sNum;
};
//静态成员属性类外初始化
int Person::sNum = 0;
int main(){
//1. 类名直接调用
Person::changeParam2(100);
//2. 通过对象调用
Person p;
p.changeParam2(200);
//3. 静态成员函数也有访问权限
//Person::changeParam3(100); //类外无法访问私有静态成员函数
//Person p1;
//p1.changeParam3(200);
return EXIT_SUCCESS;
}
3.const静态成员属性
- 如果一个类的成员,既要实现共享,又要实现不可改变,那就用 static const 修饰。定义静态const数据成员时,最好在类内部初始化。
class Person
{
private:
const static int mage = 10;
public:
static int mheight;
public:
Person(/* args */);
~Person();
};
int Person::mheight = 10;
int main(){
Person::mheight = 12;
cout << "Person::mheight:" << Person::mheight << endl;
Person p1,p2;
p1.mheight = 22;
cout << "Person::mheight:" << p1.mheight << endl;
cout << "Person::mheight:" << p2.mheight << endl;
return EXIT_SUCCESS;
}
4.const修饰成员函数
- 用const修饰的成员函数时,const修饰this指针指向的内存区域,成员函数体内不可以修改本类中的任何普通成员变量。
- 当成员变量类型符前用mutable修饰时例外。
class Person{
public:
Person(){
this->mAge = 0;
this->mID = 0;
}
//在函数括号后面加上const,修饰成员变量不可修改,除了mutable变量
void sonmeOperate() const{
//this->mAge = 100;
this->mID = 100;
}
void ShowPerson(){
cout << "ID:" << mID << " mAge:" << mAge << endl;
}
private:
int mAge;
mutable int mID;
};
int main(){
Person person;
person.sonmeOperate();
person.ShowPerson();
system("pause");
return EXIT_SUCCESS;
}
5.const修饰对象
- 常对象只能调用const的成员函数
- 常对象可访问 const 或非 const 数据成员,不能修改,除非成员用mutable修饰
class Person{
public:
Person(){
this->mAge = 0;
this->mID = 0;
}
void ChangePerson() const{
mAge = 100;
mID = 100;
}
void ShowPerson(){
this->mAge = 1000;
cout << "ID:" << this->mID << " Age:" << this->mAge << endl;
}
public:
int mAge;
mutable int mID;
};
void test(){
const Person person;
//1. 可访问数据成员
cout << "Age:" << person.mAge << endl;
//person.mAge = 300; //不可修改
person.mID = 1001; //但是可以修改mutable修饰的成员变量
//2. 只能访问const修饰的函数
//person.ShowPerson();
person.ChangePerson();
}
5.C++面向对象模型
1.成员变量和函数的存储
- C++中数据和函数是分开存储的
- c++中的非静态数据成员直接内含在类对象中,就像c struct一样。
- 成员函数(member function)虽然内含在class声明之内,却不出现在对象中。
- 每一个非内联成员函数(non-inline member function)只会诞生一份函数实例.
class MyClass01{
public:
int mA;
};
class MyClass02{
public:
int mA;
static int sB;
};
class MyClass03{
public:
void printMyClass(){
cout << "hello world!" << endl;
}
public:
int mA;
static int sB;
};
class MyClass04{
public:
void printMyClass(){
cout << "hello world!" << endl;
}
static void ShowMyClass(){
cout << "hello world!" << endl;
}
public:
int mA;
static int sB;
};
int main(){
MyClass01 mclass01;
MyClass02 mclass02;
MyClass03 mclass03;
MyClass04 mclass04;
cout << "MyClass01:" << sizeof(mclass01) << endl; //4
//静态数据成员并不保存在类对象中
cout << "MyClass02:" << sizeof(mclass02) << endl; //4
//非静态成员函数不保存在类对象中
cout << "MyClass03:" << sizeof(mclass03) << endl; //4
//静态成员函数也不保存在类对象中
cout << "MyClass04:" << sizeof(mclass04) << endl; //4
return EXIT_SUCCESS;
}
//结果:
//MyClass01:4
//MyClass02:4
//MyClass03:4
//MyClass04:4
2.this工作原理
- 由1可知C++中数据和操作是分开存储的,并且每一个非内联成员函数只会诞生一份函数实例,也就是说同一个类的多个对象会使用同一块代码
- C++提供this指针,this指针指向被调用的成员函数所属的对象。
- this指针隐含在每个类的非静态成员函数中。静态成员函数内部没有this指针,静态成员函数不能操作非静态成员变量。
3.this指针的使用
- 当形参和成员变量同名时,可用this指针来区分
- 在类的非静态成员函数中返回对象本身,可使用return *this.
6.友元
1.友元语法
- friend关键字只出现在声明处
- 其他类、类成员函数、全局函数都可声明为友元
- 友元函数不是类的成员,不带this指针
- 友元函数可访问对象任意成员属性,包括私有属性
class Building;
//友元类
class MyFriend{
public:
//友元成员函数
void LookAtBedRoom(Building& building);
void PlayInBedRoom(Building& building);
};
class Building{
//全局函数做友元函数
friend void CleanBedRoom(Building& building);
#if 0
//成员函数做友元函数
friend void MyFriend::LookAtBedRoom(Building& building);
friend void MyFriend::PlayInBedRoom(Building& building);
#else
//友元类
friend class MyFriend;
#endif
public:
Building();
public:
string mSittingRoom;
private:
string mBedroom;
};
void MyFriend::LookAtBedRoom(Building& building){
cout << "我的朋友参观" << building.mBedroom << endl;
}
void MyFriend::PlayInBedRoom(Building& building){
cout << "我的朋友玩耍在" << building.mBedroom << endl;
}
//友元全局函数
void CleanBedRoom(Building& building){
cout << "友元全局函数访问" << building.mBedroom << endl;
}
Building::Building(){
this->mSittingRoom = "客厅";
this->mBedroom = "卧室";
}
int main(){
Building building;
MyFriend myfriend;
CleanBedRoom(building);
myfriend.LookAtBedRoom(building);
myfriend.PlayInBedRoom(building);
system("pause");
return EXIT_SUCCESS;
}
注:
- 友元关系不能被继承。
- 友元关系是单向的,类A是类B的朋友,但类B不一定是类A的朋友。
- 友元关系不具有传递性。类B是类A的朋友,类C是类B的朋友,但类C不一定是类A的朋友。