C++基础——类
class 类名 { public: //不写访问标号默认是————私有的!! //行为或属性 protected: //行为或属性 private: //行为或属性 };
class和struct的区别
唯一的区别在于:struct和class的默认访问权限不同;
当我们希望定义的类的所有成员是public时,使用struct
当我们希望定义的类的成员是private时,使用class
静态成员
静态成员就是在成员变量和成员函数前加上关键字static,称为静态成员
静态成员分为:
-
静态成员变量
-
所有对象共享同一份数据
-
在编译阶段分配内存
-
类内声明,类外初始化
-
-
静态成员函数
-
所有对象共享同一个函数
-
静态成员函数只能访问静态成员变量
-
class Person { public: static int m_A; //静态成员变量 //静态成员变量特点: //1 在编译阶段分配内存 //2 类内声明,类外初始化 //3 所有对象共享同一份数据 private: static int m_B; //静态成员变量也是有访问权限的 }; int Person::m_A = 10; int Person::m_B = 10; void test01() { //静态成员变量两种访问方式 //1、通过对象 Person p1; p1.m_A = 100; cout << "p1.m_A = " << p1.m_A << endl; Person p2; p2.m_A = 200; cout << "p1.m_A = " << p1.m_A << endl; //共享同一份数据 cout << "p2.m_A = " << p2.m_A << endl; //2、通过类名 cout << "m_A = " << Person::m_A << endl; //cout << "m_B = " << Person::m_B << endl; //私有权限访问不到 } int main() { test01(); system("pause"); return 0; } class Person { public: //静态成员函数特点: //1 程序共享一个函数 //2 静态成员函数只能访问静态成员变量 static void func() { cout << "func调用" << endl; m_A = 100; //m_B = 100; //错误,不可以访问非静态成员变量 } static int m_A; //静态成员变量 int m_B; // private: //静态成员函数也是有访问权限的 static void func2() { cout << "func2调用" << endl; } }; int Person::m_A = 10; void test01() { //静态成员变量两种访问方式 //1、通过对象 Person p1; p1.func(); //2、通过类名 Person::func(); //Person::func2(); //私有权限访问不到 } int main() { test01(); system("pause"); return 0; }
数据抽象和封装
抽象是通过特定的示例抽取共同特征以后形成概念的过程。一个对象是现实世界中一个实体的抽象,一个类是一组对象的抽象。
封装是将相关的概念组成一个单元,然后通过一个名称来引用它。是保护类的成员不能被随意访问的能力。封装实现了接口与实现的分离
几个重要名词:
(1) 类名
遵循一般的命名规则; 字母,数字和下划线组合,不要以数字开头。
(2) 类成员
类可以没有成员,也可以定义多个成员。成员可以是数据、函数或类型别名。所有的成员都必须在类的内部声明。 没有成员的类是空类,空类也占用空间。
class People { }; sizeof(People) = 1;
(3) 构造函数
构造函数是一个特殊的、与类同名的成员函数,用于给每个数据成员设置适当的初始值。
(4) 成员函数
成员函数必须在类内部声明,可以在类内部定义,也可以在类外部定义。如果在类内部定义,就默认是内联函数。
(5)可使用类型别名来简化类
除了定义数据和函数成员之外,类还可以定义自己的局部类型名字。
使用类型别名有很多好处,它让复杂的类型名字变得简单明了、易于理解和使用,还有助于程序员清楚地知道使用该类型的真实目的。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
class People { public: typedef std::string phonenum; //电话号码类型 phonenum phonePub; //公开号码 private: phonenum phonePri;//私人号码 };
(6)成员函数可被重载
可以有多个重载成员函数,个数不限
(7)内联函数
有三种:
(1)直接在类内部定义。 (2)在类内部声明,加上inline关键字,在类外部定义。 (3)在类内部声明,在类外部定义,同时加上inline关键字。注意:此种情况下,内联函数的定义通常应该放在类定义的同一头文件中,而不是在源文件中。这是为了保证内联函数的定义在调用该函数的每个源文件中是可见的。
(8)访问限制
public,private,protected 为属性/方法限制的关键字。
**(9)类的数据成员中不能使用 auto、extern和register等进行修饰, 也不能在定义时进行初始化**
如 int xPos = 0; //错;
例外: 静态常量整型(包括char,bool)数据成员可以直接在类的定义体中进行初始化,例如:
static const int ia= 30;
0x02类的声明与定义
1.类声明(declare)
class Screen;
在声明之后,定义之前,只知道Screen是一个类名,但不知道包含哪些成员。只能以有限方式使用它,不能定义该类型的对象,只能用于定义指向该类型的指针或引用,声明(不是定义)使用该类型作为形参类型或返回类型的函数。
void Test1(**Screen**& a){};
void Test1(**Screen*** a){};
2.类定义(define) 在创建类的对象之前,必须完整的定义该类,而不只是声明类。所以,类不能具有自身类型的数据成员,但可以包含指向本类的指针或引用。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 class LinkScreen 2 { 3 public: 4 Screen window; 5 LinkScreen* next; 6 LinkScreen* prev; 7 }; //注意,分号不能丢 8 因为在类定义之后可以接一个对象定义列表,可类比内置类型,定义必须以分号结束: 9 class LinkScreen{ /* ... */ }; 10 class LinkScreen{ /* ... */ } scr1,scr2;
3.类对象
定义类对象时,将为其分配存储空间。
Sales_item item; //编译器分配了足以容纳一个 Sales_item 对象的存储空间。item 指的就是那个存储空间。
4.隐含的 this 指针
成员函数具有一个附加的隐含形参,即 this指针,它由编译器隐含地定义。成员函数的函数体可以显式使用 this 指针
5.何时使用 this 指针
this指针和*this的区别
this 这是指代本对象 this本来是个指针,* this的星号是取指针指向的内容, 所以, * this是整个对象,而this是指向本对象的指针
当我们需要将一个对象作为整体引用而不是引用对象的一个成员时。最常见的情况是在这样的函数中使用 this:该函数返回对调用该函数的对象的引用。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
class Screen { ... public: Screen& set(char); }; Screen& Screen::set(char c) { contents[cursor] = c; return *this; }
6.类作用域
每个类都定义了自己的作用域和唯一的类型。
类的作用域包括:类的内部(花括号之内), 定义在类外部的成员函数的参数表(小括号之内)和函数体(花括号之内)。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
class Screen { //类的内部 ... }; //类的外部 char Screen::get(index r, index c) const { index row = r * width; // compute the row location return contents[row + c]; // offset by c to fetch specified character }
0X03构造函数
构造函数的调用规则
1.创建一个类,C++编译器会给每个类至少添加3个函数
默认构造函数(空实现)
析构函数(空实现)
拷贝构造(值拷贝)
2.如果我们写了有参构造函数,编译器就不再提供默认构造,依然提供拷贝构造
如果我们写了拷贝构造函数,编译器就不再提供其他构造函数
构造函数是特殊的成员函数,用来保证每个对象的数据成员具有合适的初始值。 构造函数名字与类名相同,不能指定返回类型(也不能定义返回类型为void),可以有0-n个形参。 在创建类的对象时,编译器就运行一个构造函数。
3.1构造函数的4种形式
Sales_data() = default; Sales_data(const std::string &book):bookNo(book){} Sales_data(const std::string& book, const unsigned num, const double sellp, const double salesp); Sales_data(std::istream& is);
3.1.2拷贝构造函数
class Person{
public:
Person(const Person &p)
{
}
};
//注意事项
//调用默认构造函数的时候,不要加()
//因为加了(),编译器会认为是一个函数的声明 Person p1();
匿名对象
Person(10); //当这条语句执行结束后,系统会立即回收掉匿名对象
//注意事项
//不要利用拷贝构造函数 初始化匿名对象
3.1.3拷贝构造函数调用时机
C++中拷贝构造函数调用时机通常有三种情况
-
使用一个已经创建完毕的对象来初始化一个新对象
-
值传递的方式给函数参数传值
-
以值方式返回局部对象
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
class Person { public: Person() { cout << "无参构造函数!" << endl; mAge = 0; } Person(int age) { cout << "有参构造函数!" << endl; mAge = age; } Person(const Person& p) { cout << "拷贝构造函数!" << endl; mAge = p.mAge; } //析构函数在释放内存之前调用 ~Person() { cout << "析构函数!" << endl; } public: int mAge; }; //1. 使用一个已经创建完毕的对象来初始化一个新对象 void test01() { Person man(100); //p对象已经创建完毕 Person newman(man); //调用拷贝构造函数 Person newman2 = man; //拷贝构造 //Person newman3; //newman3 = man; //不是调用拷贝构造函数,赋值操作 } //2. 值传递的方式给函数参数传值 //相当于Person p1 = p; void doWork(Person p1) {} void test02() { Person p; //无参构造函数 doWork(p); } //3. 以值方式返回局部对象 Person doWork2() { Person p1; cout << (int *)&p1 << endl; return p1; } void test03() { Person p = doWork2(); cout << (int *)&p << endl; } int main() { //test01(); //test02(); test03(); system("pause"); return 0; }
3.1.3深拷贝与浅拷贝
浅拷贝:简单的赋值拷贝操作
深拷贝:在堆区重新申请空间,进行拷贝操作
解决办法:
给p2的m_height重新分配一块新的内存,这样p2调用析构的时候,释放的就是p2的,而不是共用的那块
Person(const Person& p) //拷贝构造 { cout << "p的拷贝构造函数" << endl; m_age = p.m_age; //m_height = p.m_height; 编译器的做法 m_height = new int(*p.m_height); //给它重新分配一块内存,让浅拷贝变成深拷贝 }
3.2构造函数可以重载
可以为一个类声明的构造函数的数量没有限制,只要每个构造函数的形参表是唯一的。
class Sales_item; { public: Sales_item(const std::string&); Sales_item(std::istream&); Sales_item(); //默认构造函数 };
3.3构造函数自动执行
只要创建该类型的一个对象,编译器就运行一个构造函数:
Sales_item item1("0-201-54848-8");
Sales_item *p = new Sales_item();
第一种情况下,运行接受一个 string 实参的构造函数,来初始化变量item1。
第二种情况下,动态分配一个新的 Sales_item 对象,通过运行默认构造函数初始化该对象。
3.4 哪种类需要初始化式
const 对象或引用类型的对象,可以初始化,但不能对它们赋值,而且在开始执行构造函数的函数体之前要完成初始化。
初始化 const 或引用类型数据成员的唯一机会是构造函数初始化列表中,在构造函数函数体中对它们赋值不起作用。 没有默认构造函数的类类型的成员,以及 const 或引用类型的成员,必须在初始化列表中完成初始化。
class ConstRef { public: ConstRef(int ii); private: int i; const int ci; int &ri; }; ConstRef::ConstRef(int ii) { i = ii; // ok ci = ii; // error ri = i; // error }
应该这么初始化:
ConstRef::ConstRef(int ii): i(ii), ci(i), ri(ii) { }
3.5 合成的默认构造函数
只有当一个类没有定义构造函数时,编译器才会自动生成一个默认构造函数。
一个类只要定义了一个构造函数,编译器也不会再生成默认构造函数。
建议:
如果定义了其他构造函数,也提供一个默认构造函数。
如果类包含内置或复合类型(如 int& 或 string*)的成员,它应该定义自己的构造函数来初始化这些成员。每个构造函数应该为每个内置或复合类型的成员提供初始化。
3.6类对象作为类成员
当其他类的成员作为本类的对象时,先构造其他类,再构造自身 析构顺序相反
#include<iostream> #include<string> using namespace std; class Phone { public: Phone(string name) :p_name(name) { cout << "Phone类的构造函数" << endl; } string p_name; }; class Person { public: //Phone m_phone=phone; 隐式转换法 Person(string name, string phone) :m_name(name), m_phone(phone) { cout << "Person类的构造函数" << endl; } string m_name; Phone m_phone; }; //当其他类的成员作为本类的对象时,先构造其他类,再构造自身 //析构顺序相反 void test01() { Person p("张三", "Iphone"); cout << p.m_name << "," << p.m_phone.p_name << endl; } int main() { test01(); return 0; }
0X04this指针
在C++中,类内的成员变量和成员函数分开存储
只有非静态成员变量才属于类的对象上
每一个非静态成员函数只会诞生一份函数实例,也就是说多个同类型的对象会共用一块代码
那么问题是:这一块代码是如何区分那个对象调用自己的呢?
c++通过提供特殊的对象指针,this指针,解决上述问题。this指针指向被调用的成员函数所属的对象(谁调用就指向谁)
this指针是隐含每一个非静态成员函数内的一种指针
this指针不需要定义,直接使用即可
this指针的用途:
-
当形参和成员变量同名时,可用this指针来区分
-
在类的非静态成员函数中返回对象本身,可使用return *this
class Person { public: Person(int age) { //1、当形参和成员变量同名时,可用this指针来区分 this->age = age; } Person& PersonAddPerson(Person p) //如果不加引用,返回的是一个临时的副本值,它拷贝p2的值,因此p2本身并没有变化,变化的是p2的副本 { this->age += p.age; //返回对象本身 return *this; } int age; }; void test01() { Person p1(10); cout << "p1.age = " << p1.age << endl; Person p2(10); p2.PersonAddPerson(p1).PersonAddPerson(p1).PersonAddPerson(p1); cout << "p2.age = " << p2.age << endl; } int main() { test01(); system("pause"); return 0; }
0x05常函数与常对象
常函数:(本质就是给this指针加了一个const,使它指向的对象和自己本手都不可以修改——底层const+顶层const)
-
成员函数后加const后就叫常函数
-
常函数内部不可以修改成员属性
-
成员属性声明时加mutable后,在常函数中依然可以修改
常对象:
-
声明对象前加const修饰
-
常对象只能调用常函数
-