- 什么是类(class)?
类(class)是类型(type),是用户自定义的类型。为什么不叫它type,因为借用Simula语言中的class关键字。 - 为什么要有类?
基于便利性的考虑,现实世界中物(object)通常被分为几种独立的分类。 - 基本概念
| 概念 | 比喻 |
|---|---|
| 对象/实例 | 楼房 |
| 实例化 | 建造 |
| 类 | 图纸 |
- 面向对象四大特征
| 特征 | 说明 | 类比 |
|---|---|---|
| 抽象 | 抽出具体事物的普遍性的本质 | 分门别类:鸟类、哺乳类、鱼类 |
| 封装 | 把数据与处理(函数)包在一起 | 通信录(增加、删除) |
| 继承 | 数据与处理(函数)的传承 | 财富与绝技、混血儿(肤色/头发)、 两种语言 |
| 多态 | 同一个事物(函数)的多种形态 | 手机键盘数字与字母、 电脑键盘功能键 |
class 类名{
成员变量成员函数声明
};
class定义最后的;一定不要忘记。
- 构成
| 构成 | 作用 |
|---|---|
| 数据成员(data member)/成员变量/属性 | 对象内部数据和状态,只能在类定义中声明,可以在成员函数中直接调用。 |
| 成员函数/方法 | 对象相关的操作,可以在类内实现或类外实现。 |
- 访问限定符
| 限定符 | 作用 |
|---|---|
| private[默认] | 私有 |
| public | 公开 |
| protected | 保护 |
注:实践中,成员变量多数情况使用private或者protected,成员函数多数情况使用public。通常,通过成员函数改变对象的成员变量。
- 类定义与类实现分离
1、头文件 – 声明
方式:#pragma once或者#ifnde...#endif
作用:防止头文件二次编译
实例:
complex.h
#ifndef __COMPLEX_H
#define __COMPLEX_H
class Complex{
private:
int real;
int imag;
public:
void SetReal(int r);
void SetImag(int i);
int GetReal();
int GetImag();
void Print();
Complex Add(Complex c1);
};
Complex Add(Complex c1,Complex c2);
#endif //__COMPLEX_H
2、源文件 – 实现
引用头文件:#include <>(标准库函数)/#include ""(自定义/第三方函数)
注:在C++书籍中为了方便.h与.cpp不做分离,但是项目开发中,需要分开。
- 对象做参数和返回值
Complex Add(Complex c1,Complex c2); Complex Complex::Add(Complex c);
- 实例:复数
complex.cpp:
#include#include "complex.h" //引用头文件 using namespace std; void Complex::SetReal(int r) { real = r; } void Complex::SetImag(int i) { imag = i; } int Complex::GetReal() { return real; } int Complex::GetImag() { return imag; } void Complex::Print() { cout << real << "+" << imag << "i" << endl; } Complex Complex::Add(Complex c1) { Complex res; //成员函数可以直接访问自身的成员变量 //res.SetReal(real+c1.GetReal()); //res.SetImag(imag+c1.GetImag()); //成员函数可以直接访问自身的成员函数 //res.SetReal(GetReal()+c1.GetReal()); //res.SetImag(GetImag()+c1.GetImag()); //在成员函数中,当前类作为参数传入时,对象可以直接访问自身的私有成员 //res.SetReal(real+c1.real); //res.SetImag(imag+c1.imag); res.real = real + c1.GetReal(); res.imag = imag + c1.GetImage(); return res; } Complex Add(Complex c1,Complex c2) { Complex c; c.SetReal(c1.GetReal()+c2.GetReal()); c.SetImag(c1.GetImag()+c2.GetImag()); return c; }
main.c:
#include "complex.h"
int main(){
Complex c;
c.SetReal(3);
c.SetImag(5);
c.Print();
Complex c2;
c2.SetReal(6);
c2.SetImag(8);
c2.Print();
Complex c3 = Add(c,c2);
c3.Print();
Complex c4 = c.Add(c2);
c4.Print();
}
实例1:复数(未拆分)
#includeusing namespace std; class Complex{ private: int real; int imag; public: void SetReal(int r){ real = r; } void SetImag(int i){ imag = i; } int GetReal(){ return real; } int GetImag(){ return imag; } void Print(){ cout << real << "+" << imag << "i" << endl; } Complex Add(Complex c1){ Complex res; //成员函数可以直接访问自身的成员变量 //res.SetReal(real+c1.GetReal()); //res.SetImag(imag+c1.GetImag()); //成员函数可以直接访问自身的成员函数 //res.SetReal(GetReal()+c1.GetReal()); //res.SetImag(GetImag()+c1.GetImag()); //在成员函数中,当前类作为参数,该对象可以直接访问自身的私有成员(c1和当前类属于同一类型,且只能在成员函数中访问) res.SetReal(real+c1.real); res.SetImag(imag+c1.imag); //res.real = real + c1.GetReal(); //res.imag = imag + c1.GetImage(); return res; } }; Complex Add(Complex c1,Complex c2){ Complex c; c.SetReal(c1.GetReal()+c2.GetReal()); c.SetImag(c1.GetImag()+c2.GetImag()); return c; } int main(){ Complex c; c.SetReal(3); c.SetImag(5); c.Print(); Complex c2; c2.SetReal(6); c2.SetImag(8); c2.Print(); Complex c3 = Add(c,c2); c3.Print(); Complex c4 = c.Add(c2); c4.Print(); }
实例2:账单
#include2.2.2 C++class与struct区别#include using namespace std; class Bill{ public: void SetName(const char* s){ strcpy(name,s); } void SetNum(int n){ num = n; } void SetPrice(float p){ price = p; } float GetTotalPrice(){ return num*price; } void Print(){ cout << name << 't' << num << "t¥" << price << 't' << GetTotalPrice() << endl; } void Scan(){ cin >> name >> num >> price; } int GetNum(){ return num; } private: char name[20]; int num; float price; }; int main(){ int n; cin >> n; Bill b[n]; for(int i = 0;i < n;++i){ b[i].Scan(); } cout << "商品t数量t单价t小计" << endl; float sum(0); int num(0); for(int i = 0;i < n;++i){ num += b[i].GetNum(); sum += b[i].GetTotalPrice(); b[i].Print(); } cout << "----------------------------------" << endl; cout << "共计" << num << "件ttt¥" << sum << endl; }
- 1、默认的访问控制不同
struct是public,class默认是private - 2、struct可以使用花括号内的初始值列表{...}初始化,class不可以(C++98不可以,C++11可以)。
注:
(1)C++的struct可以有成员函数,而C不可以。
(2)C++的struct可以使用访问控制关键字(public private protected),而C不可以。
(3)C++的struct成员变量默认初始化为随机值(主要影响指针)。
- 直接创建 – 类作为类型定义变量 – 栈上创建
例如:
1、基本类型
//c int a = 10; int b(10); //c++ int a(0);// 等价 int a = 0; const float b(1.0);// 等价 const float b = 1.0;
2、类类型
// 定义类
class Demo{};
// 创建对象
int main(){
Demo d; // 变量(命名对象)
Demo(); // 匿名对象
}
基本语法:
类名 对象名; // 调用默认构造函数 类名(); // 创建匿名对象
- 动态创建 – 堆上创建
1、基本类型
int* p = new int; delete p; p = NULL;
2、类类型
// 定义类
class Demo{};
// 创建对象
int main(){
Demo* d = new Demo;
delete d;
d = NULL;
}
基本语法:
类名* 对象指针 = new 类名;// 调用默认构造函数 delete 对象指针;
对象指针new可以为对象设置初始值,例如下面代码
int* p = new int(100); cout << *p << endl;
- 动态创建数组 – 堆上创建
1、基本类型
int* pa = new int[10]; delete pa;// 只释放p[0] delete [] pa;// 释放全部数组
2、类类型
// 定义类
class Demo{};
// 创建对象
int main(){
Demo* d = new Demo[10];
delete [] d;
d = NULL;
}
对象数组指针new不可以为对象设置初始值。
int* pa = new int[10](100); // error: array 'new' cannot have initialization arguments
注意:C++除了特殊情况,很少直接使用malloc()/free()申请释放内存,取而代之的是new/delete。
2.3 this指针作用域:类内部
特点:
1、类的一个自动生成、自动隐藏的私有成员
2、每个对象仅有一个this指针
3、当一个对象被创建时,this指针就存放指向对象数据的首地址
4、不是对象本身的一部分,不会影响sizeof(对象)的结果
注:如果成员函数形参与成员变量同名,使用this->做为前缀区分。
- 语法:
类名(参数){
函数体
}
- 实例:
Complex():real(0),imag(0){}
- 特点:
1、在对象被创建时自动执行
2、构造函数的函数名与类名相同
3、没有返回值类型、也没有返回值
4、可以有多个构造函数 - 调用时机
1、对象直接定义创建–构造函数不能被显式调用
2、new动态创建 - 默认构造函数
类中没有显式的定义任何构造函数,编译器就会自动为该类型生成默认构造函数,默认构造函数没有参数。 - 构造函数的三个作用
1、给创建的对象建立一个标识符
2、为对象数据成员开辟内存空间
3、完成对象数据成员的初始化 - 构造函数初始化成员列表
语法:
类名(参数):成员变量(参数){
函数体
}
实例:
Complex(int r,int i):real(r),imag(i){}
作用:
初始化非静态成员变量
说明:
从概念上来讲,构造函数的执行可以分成两个阶段,初始化阶段和计算阶段,初始化阶段先于计算阶段。
加入构造函数后,对象可以用以下方式定义并初始化:
#include "complex.h"
int main(){
int n(10); //int n - 10;
//Complex c;
//c.SetReal(3);
//c.SetImag(5);
Complex C(3,5);
c.Print();
Complex c2(6,8);
c2.Print();
Complex c3 = Add(c,c2);
c3.Print();
Complex c4 = c.Add(c2);
c4.Print();
}
- 必须使用初始化列表的情况:
1、常量成员,因为常量只能初始化不能赋值,所以必须放在初始化列表里面。
2、引用类型,引用必须在定义的时候初始化,并且不能重新赋值,所以也要写在初始化列表里面。
3、没有默认构造函数的类类型,因为使用初始化列表可以不必调用默认构造函数来初始化,而是直接调用拷贝构造函数初始化。
注:能使用初始化列表的时候尽量使用初始化列表
- 语法:
~类名(){
函数体
}
1、析构函数的函数名与类名相同
2、函数名前必须有一个~
3、没有参数
4、没有返回值类型、也没有返回值
5、只能有一个析构函数
-
调用时机
对象离开作用域
delete -
默认析构函数
类中没有显式的定义析构函数,编译器就会自动为该类型生成默认析构函数 -
作用
释放对象所申请占有的资源 -
实例:
#include2.4.3 引用using namespace std; class Simple{ int n; public: Simple(int n):n(n){ cout << "n:" << n << endl; this->n = n; cout << "n:" << n << endl; } ~Simple(){ cout << "~Simple()" << endl; } void SetN(int n){ //void SetN(Simple* this,int num){ this->n = n; // this->n = num; } //} int GetN(){ return n; } void Print(){ cout << this << ":" << n << endl; } }; int main(){ Simple s(10); }
- 语法:
const 类型名& 对象名/类型名& 对象名
- 使用:
与对象变量、基本类型变量一样 - 实例1:
#includeusing namespace std; void swap(int* a,int* b){ int t = *a; *a = *b; *b = t; } void swap2(int& a,int& b){ cout << "&a:" << &a << endl; cout << "&b:" << &b << endl; int t = a; a = b; b = t; } int main(){ int n = 10; int* p = NULL; p = &n; cout << "*p:" << *p << endl; cout << "n:" << n << endl; *p = 20; cout << "n:" << n << endl; int& r = n; cout << "r=" << r << endl; r = 30; cout << "r:" << r << endl; cout << "n:" << n << endl; cout << "*p:" << *p << endl; //引用就是一个别名,n和r地址相同,对r操作相当于对n操作,也相当于对*p操作 cout << "&r:" << &r << endl; cout << "&n:" << &n << endl; cout << "p:" << p << endl; int x = 10,y =20; cout << "x:" << x << "t" << "y:" << y << endl; swap(&x,&y); cout << "x:" << x << "t" << "y:" << y << endl; swap2(x,y);// int& a = x;int& b = y; cout << "x:" << x << "t" << "y:" << y << endl; cout << "&x:" << &x << endl; cout << "&y:" << &y << endl; }
*p:10 n:10 n:20 r=20 r:30 n:30 *p:30 &r:0x7ffca409a4ac &n:0x7ffca409a4ac p:0x7ffca409a4ac x:10 y:20 x:20 y:10 &a:0x7ffca409a4a8 &b:0x7ffca409a4a4 x:10 y:20 &x:0x7ffca409a4a8 &y:0x7ffca409a4a4
引用其实就是一个别名,a与b代表的是相同的对象。
- 何处使用引用
1、函数参数列表
2、函数返回值
3、成员变量 – 对象初始化时,必须显示初始化的变量 - 为何使用引用
1、避免对象复制
2、避免传递空指针
3、使用方便 - 引用作用
取代指针
void Func(int* n){
*n = 2;
}
void Func(int& n){
n = 3;
cout << &n << endl;
}
int main(){
int a =1;
cout << &a << endl;
Func(&a);
cout << a << endl;
Func(a);
cout << a << endl;
}
-
函数的三种传参方式
1、传值void Func(int n)
2、传地址/指针void Func(int* n)
3、传引用void Func(int& n) -
引用与指针的区别
1、指针指向一块内存,它的内容是所指内存的地址;引用是某块内存的别名。
2、引用只能在定义时被初始化一次,之后不可变;指针可变;
3、引用不能为空,指针可以为空;
4、引用使用时无需解引用*,指针需要解引用;
5、sizeof 引用得到的是所指向的变量/对象的大小,而sizeof 指针得到的是指针本身的大小;
6、对于引用类型的成员变量,所属类的大小时按照指针大小计算,自身大小按照自身类型计算。
- 语法:
类名(类名& 形参){
函数体
}
或
类名(const 类名& 形参){
函数体
}
- 调用时机
1、手动调用
类名 对象名; // 调用默认构造函数 类名 对象2 = 对象1; // 调用复制构造函数 类名 对象3(对象1); // 调用复制构造函数
2、自动调用
(1)一个对象作为函数参数,以值传递的方式传入函数体
(2)一个对象作为函数返回值,以值从函数返回
(3)一个对象拷贝构造,它的成员对象自动调用拷贝构造
(4)子对象拷贝构造父对象自动拷贝构造
- 实例:分别采用C和C++实现栈的创建、出栈、入栈等操作
C语言写法stack.c
#include#include #include typedef int Element; typedef struct Stack{ Element* data; size_t size; }Stack; Stack* Create(){ Stack* s = malloc(sizeof(Stack)); s->data = NULL; s->size = 0; return s; } bool Push(Stack* s,Element e){ if(NULL == s){ return false; } s->data = realloc(s->data,sizeof(Element)*(s->size+1)); if(NULL == s->data) return false; s->data[s->size] = e; s->size++; return true; } Element* Top(Stack* s){ if(NULL == s || NULL == s->data) return NULL; return &s->data[s->size-1]; } bool Pop(Stack* s){ if(NULL == s || NULL == s->data) return NULL; s->size--; s->data = realloc(s->data,sizeof(Element)*s->size); if(s->data != NULL) return true; else return false; } size_t GetSize(Stack* s){ return s->size; } void Destory(Stack** s){ if(NULL == *s || NULL == s) return; free((*s)->data); free(*s); *s = NULL; } int main(){ Stack* s = Create(); for(;;){ Element e; int num = scanf("%d",&e); if(EOF == num) break; Push(s,e); } while(GetSize(s) != 0){ printf("%d ",*Top(s)); Pop(s); } printf("n"); Destory(&s); }
C++写法 stack.cpp
#include2.4.5 默认拷贝构造函数#include using namespace std; typedef int Element; class Stack { Element* data; size_t size; public: // 构造函数 Stack(){ cout << this << ":Stack constructor" << endl; data = NULL; size = 0; } // 拷贝构造函数 Stack(const Stack& s){ cout << "Stack copy constructor" << endl; size = s.size; data = s.data; } // 析构函数 ~Stack(){ cout << this << ":Stack destructor" << endl; delete [] data; data = NULL; size = 0; } bool Push(Element e) { Element* tmp = new Element[size+1]; if(NULL == tmp) return false; memcpy(tmp,data,sizeof(Element)*size); tmp[size] = e; delete [] data; data = tmp; ++size; return true; } Element* Top() { if(NULL == data) return NULL; return &data[size-1]; } bool Pop() { if(NULL == data) return false; size--; Element* tmp = new Element[size]; if(NULL == tmp) return false; memcpy(tmp,data,sizeof(Element)*size); delete [] data; data = tmp; return true; } size_t GetSize() { return size; } void Destory() { delete [] data; data = NULL; size = 0; } }; int main() { Stack s; for(;;) { Element e; if(!(cin >> e)) break; s.Push(e); } while(s.GetSize() != 0) { cout << *s.Top() << " "; s.Pop(); } cout << endl; s.Destory(); //Stack t = s; //调用拷贝构造函数 //Stack t(s); //拷贝构造 //Func(s); //Stack t = s; //拷贝构造 Stack t = Return(); //真实执行拷贝构造但是被编译器优化了 //cout << "&t:" << &t << endl; } }
- 本质:
内存拷贝 - 作用:
复制一个已经存在的对象 - 实例1:
#include#include using namespace std; class Test { public: Test() {} Test(Test &t) { cout << "Test(Test &t)" << endl; } Test(Test const &t) { cout << "Test(Test const &t)" << endl; } }; int main() { Test t1; Test t2 = t1; const Test t3; Test t4 = t3; }
Test(Test &t) Test(Test const &t)
- 实例2:
#include#include // 禁用编译优化g++ test.cpp -fno-elide-constructors using namespace std; class Test { public: Test(); Test(const Test &t); ~Test(); //private: // Test(const Test &t); //禁用拷贝构造函数 }; Test::Test() { cout << this << ":constructor" << endl; } Test::Test(const Test &t) { cout << this << ":copy constructor" << endl; } Test::~Test() { cout << this << ":destructor" << endl; } Test Return() { Test t; return t; //NRVO: Name Return Value Option //return Test(); //RVO: Return Value Option } int main() { cout << "1" << endl; { cout << "2" << endl; //Test t; //Test(); //匿名对象 临时对象 生存周期只在当前语句 Test t1 = Return(); cout << "3" << endl; } cout << "4" << endl; }
1 2 0x7ffe3671285f:constructor 0x7ffe3671288f:copy constructor 0x7ffe3671285f:destructor 0x7ffe3671288e:copy constructor 0x7ffe3671288f:destructor 3 0x7ffe3671288e:destructor 42.5 赋值运算符重载函数
- 语法
类名& operater=(const 类名& 形参){
// 赋值操作
return *this;
}
加上引用后可以有效的避免函数调用时对实参的拷贝,提高了程序的效率。
- 调用时机:
类对象进行赋值的时候 - 作用:
赋值 - 拷贝构造函数与赋值操作符的区别
1、拷贝构造函数:用一个已经存在的对象来初始化一个不曾存在的对象
2、赋值操作符:当两个对象都已经存在 - 实例1:
#includeusing namespace std; class Simple{ public: Simple(){ cout << "construtor" << endl; } explicit Simple(int n){ //explicit关键字,禁用构造函数的默认转换 cout << "construtor " << n << endl; } Simple(const Simple& s){ cout << "copy construtor" << endl; } //赋值运算符重载 Simple& operator=(const Simple& s){ cout << "assign" << endl; return *this; // 返回当前对象 } ~Simple(){ cout << "destructor" << endl; } //禁用拷贝构造函数和赋值运算符重载的方法一:删除函数 //Simple(const Simple& s) = delete; // 禁用拷贝构造函数的方法 //Simple& operator=(const Simple& s) = delete; // 禁用赋值运算符重载的方法 //禁用拷贝构造函数和赋值运算符重载的方法二:加入私有 }; void Func(Simple s){} int main(){ Simple s; Simple t; Simple w; w = t = s; // t = s; //返回t // w = t; //返回w Simple n1(1); Simple n2(2); //构造函数加入关键字后,只能这么写 //Simple n3 = 2; // 构造函数加入explicit后不支持这种写法 //类似操作: //Simple temp(2); //Simple n3 = temp; //Func(3); // Simple s = 3; //explicit,禁用构造函数的默认转换 Func(Simple(3)); }
实例2:
#includeusing namespace std; class Test{ public: Test(int i){ cout << this << " constructor " << i << endl; } Test(const Test& t){ cout << this << " copy onstructor " << &t << endl; } //赋值运算符重载 Test& operator=(const Test& t){ cout << this << " assign " << &t << endl; return *this; } ~Test(){ cout << this << " destructor " << endl; } }; void Func(const Test& t){} void Function(int a,int b = 1,int c = 2){ cout << a << " "<< b << " " << c << endl; }; int main(){ //Test a(1); //Test a = 1; //近似过程:Test(1);Test a = Test(1); //g++ test3.cpp -fno-elide-constructors可以看到构造过程 //Func(1); // 近似:cosnt Test& t = Test(1); Test a(1); a = 2; Function(100); Function(100,200); Function(100,200,300); }
0x7ffe4dd6974e constructor 1 0x7ffe4dd6974f constructor 2 0x7ffe4dd6974e assign 0x7ffe4dd6974f 0x7ffe4dd6974f destructor 100 1 2 100 200 2 100 200 300 0x7ffe4dd6974e destructor2.6 深拷贝与浅拷贝
- 浅拷贝:
编译器默认生成的类实例间拷贝行为,对带有指针的类来说会引发 memory leak(内存泄漏)。 - 深拷贝:
用户定义的行为(实质是一种构造函数)。 - 区别:
浅拷贝:只拷贝指针地址
深拷贝:重现分配堆内存,拷贝指针指向内容
注:不涉及指针,空间分配等资源问题时,深浅拷贝无区别 - 浅拷贝存在的问题:
当类中存在指针或者动态的内存分配时,使用浅拷贝只会将那块内存的位置告知当前对象,并不会重新为新对象分配内存。当程序运行结束后,两个对象分别析构,此时这同一块内存将被释放两次。释放第二次时,析构对象找不到需要释放的内存,就会导致内存泄漏。
实例:
#includeusing namespace std; class Demo{ public: Demo(int n){ p = new int(n); } ~Demo(){ cout << p << endl; delete p; p = NULL; } private: int* p; }; int main(){ Demo a(10); Demo b = a; }
0x13c6e70 0x13c6e70 free(): double free detected in tcache 2 Aborted (core dumped)
问题分析:运行结果可以看出,对象a的指针p和对象b的指针p指向同一块内存,程序结果时,同一内存被delete两次导致程序运行出错。
- 解决方式:
深拷贝,重新申请空间,让拷贝对象指向新空间
#includeusing namespace std; class Demo{ public: Demo(int n){ cout << "constructor" << endl; p = new int(n); } Demo(const Demo& d){ cout << "deep copy" << endl; //创造新的空间,再进行copy操作 p = new int; *p = *d.p; } ~Demo(){ cout << p << endl; delete p; p = NULL; } int GetData(){ return *p; } private: int* p; }; int main(){ Demo a(10); Demo b = a; cout << a.GetData() << "t" << b.GetData() << endl; }
constructor deep copy 10 10 0x23462a0 0x2346280
- 最佳实践:
三大定律(Rule of three / the Law of The Big Three / The Big Three)
如果类中明确定义下列其中一个成员函数,那么必须连同其他二个成员函数编写至类内,即下列三个成员函数缺一不可:
1、析构函数(destructor)
2、复制构造函数(copy constructor)
3、复制赋值运算符(copy assignment operator)
- 作用
非成员函数访问类中的私有成员 - 分类
全局友元函数:将全局函数声明成友元函数
友元成员函数:类的提前引用声明,将一个函数声明为多个类的友元函数
友元类:将整个类声明为友元 - 特点
友元关系单向性
友元关系不可传递 - 实例:
#include2.8 const限定符 2.8.1 本质using namespace std; class Integer { //友元类 friend class Friend; //友元函数,允许访问类的私有成员 friend void Func(Integer& num); private: int n; public: Integer(int n):n(n) {} void Print() { cout << n << endl; } }; class Friend { public: void Func(Integer& num) { cout << num.n << endl; } }; void Func(Integer& num) { cout << num.n << endl; } int main() { Integer num(10); num.Print(); Func(num); Friend f; f.Func(num); }
只读(read only)
2.8.2 const与变量/对象const 类型 变量 = 初始值; const 类型 对象;
例如:
const int size = 4;
现在比较前卫写法:
类型 const 变量 = 初始值; 类型 const 对象;
例如:
int const size = 4;
- 定义时必须初始化
- 全局作用域声明的const变量默认作用域是定义所在文件
- const 类型参数可以接受const变量和非const变量
非const类型参数只能接受非const变量
例如:const对象只能调用const成员函数 - const与宏定义#define的区别
| 区别 | const | 宏定义#define |
|---|---|---|
| 编译器处理方式 | 编译运行阶段使用 | 预处理阶段展开/替换 |
| 类型 | 有具体的类型 | 没有类型 |
| 安全检查 | 编译阶段会执行类型检查 | 不做任何类型检查 |
| 存储方式 | 分配内存 | 不分配内存 |
| 类型 | 语法 | 作用 |
|---|---|---|
| const指针 | 类型* const 变量 = 初始值; | 指针指向地址不能改变 |
| 指向const对象的指针 | const 类型* 变量 = 初始值; 类型 const* 变量 = 初始值; | 指针指向对象不能改变 |
| 指向const对象的const指针 | const 类型* const 变量 = 初始值; | 指针指向地址和对象不能改变 |
指向const对象的指针是使用最频繁的方式。
2.8.3 const与引用类型 const &变量 = 初始值;与const 类型& 变量 = 初始值;都是引用对象不能改变。
2.8.5 const与函数的参数和返回值| 类型 | 语法 | 作用 | 说明 |
|---|---|---|---|
| const参数 | 返回值类型 函数(const 类型 形参) | 函数内部不能改变参数的值 | 这样的参数的输入值 |
| const返回值 | const 返回值类型 函数(形参列表) | 函数的返回值不能改变 | 常用于字符串/指针 |
- const成员变量
不能在类声明中初始化const数据成员(C++11可以)
const成员变量只能在类构造函数的初始化列表中初始化
class 类名{
public:
类名(类型 形参):成员变量(形参){}
private:
const 类型 成员变量;
}
应用:
const成员变量一般用于类定义后不可修改的信息,例如:学生学号。
- const成员函数
成员函数不能修改类中任何成员变量。一般写在成员函数的最后来修饰。
声明:
class 类名{
public:
返回值类型 函数名(形参列表)const;
}
定义:
返回值类型 函数名(形参列表)const;
注:必须在成员函数的声明和定义后都加上const
| const修饰位置 | 作用 |
|---|---|
| 变量 | 变量不可修改,通常用来替代#define |
| 对象/实例 | 对象的成员变量不可修改,只能调用const成员函数 |
| 函数参数 | 参数不能在函数内部修改,只作为入参 |
| 函数返回值 | 返回的结果不能被修改,常用于字符串 |
| 成员变量 | 只能在初始化列表中初始化 |
| 成员函数 | 不改变成员变量 |
注:只要能够使用const,尽量使用const
- 实例:
#includeusing namespace std; class Test { public: Test():n(100) { // n = 100 //初始化 } //如果成员函数不修改(不想修改、不能修改)成语变量,最好在函数后加const void Print()const { cout << n << endl; } int GetNum() { return n; } private: const int n; }; //const 类型参数可以接受const变量和非const变量 //非const类型参数只能接受非const变量 void Print(const char* s) { cout << s << endl; } void Print(const int& n) { cout << n << endl; } int main() { //const int n = 10; int const n = 10; //n = 30; //不允许修改 //const int m; //cosnt定义时必须初始化 //常量指针 指针指向的值不能修改 const int* p1; int const* p2; //指针常量 指针指向地址不能修改 //int* const p3; const int& f1 = n; int const& f2 = n; //int& const f3; char* s = "abc"; Print(s); const char* s1 = "ABC"; Print(s1); int m = 10; Print(m); Print(n); Print(20); const Test t; t.Print(); // cout << t.GetNum() << endl; //const对象只能调用const成员函数 Test t1; t1.Print(); //Test::Print(Test* this); // Test::Print(&t1); cout << t1.GetNum() << endl; //const类型参数可以接受const型和非const型 }
abc ABC 10 10 20 100 100 1002.9 static限定符
- 生存周期:
整个程序的生存周期。 - 作用域:
属于类,不属于对象。 - 语法:
声明:
class 类名{
static 返回类型 函数(形参列表);
};
定义:
返回类型 类名::函数(形参列表){
函数体;
}
调用:
1、通过类名(Class Name)调用
类名::函数(实参列表);
2、通过对象(Object)调用
对象.函数(实参列表);
- 规则
1、static只能用于类的声明中,定义不能标示为static。
2、非静态是可以访问静态的成员和函数
3、静态成员函数不能访问普通成员
4、静态成员函数可以设置private,public,protected访问权限 - 禁忌
1、静态成员函数不能访问非静态(非static)函数或者变量
2、静态成员函数不能使用this关键字
3、静态成员函数不能使用cv限定符(const与volatile)
因为静态成员函数是属于类而不是某个对象。
volatile是一个不常用的关键字,作用是改善编译器的优化能力。 - 静态成员变量
语法:
1、在类定义中声明,但是在类实现中初始化。
2、在声明时需要指定关键字static,但是在类外定义时不要指定static。
3、对象的大小不包含静态成员变量
因为静态成员变量是属于类而不是某个对象。静态成员变量所有类的对象/实例共享。
| static修饰位置 | 作用 |
|---|---|
| 变量 | 静态变量 |
| 函数 | 只源文件内部使用的函数 |
| 成员变量 | 对象共享变量 |
| 成员函数 | 类提供的函数,或者作为静态成员对象的接口 |
单例模式:使用静态成员变量和静态成员函数。
- 实例1:
#includeusing namespace std; class Test{ public: void Hello(); static void World(); private: int n; }; void Test::Hello(){ cout << "Hellot" << "n:" << n << endl; } //函数声明中加static,但是函数实现不能添加static void Test::World() { //静态成员函数不能添加const是因为函数本身没有传指针(const Test* this) cout << "Worldt" << << endl; //不能访问普通成员 } int main(){ Test t; t.Hello(); //类似Hello(&t); //Test::Hello(); //错误 //通过类名调用 Test::World(); //类似World() //对象调用 t.World(); //World() }
Hello n:0 World World
实例2:
#includeusing namespace std; class Test{ public: void Hello()const; static void World(); //private: static int n; //static属于类,不属于对象 }; //static只能用于类的声明中,定义不能标示为static。 int Test::n = 0; //类外初始化 void Test::Hello()const{ //类似void Test::Hello(const Test* this); cout << "Hellot" << "n:" << n << endl; } //函数声明中加static,但是函数实现不能添加static void Test::World() { //不能添加const是因为函数本身没有传指针(const Test* this) cout << "Worldt" << "n:" << n << endl; //不能访问普通(非static)成员 } int main(){ Test t; t.Hello(); //类似Hello(&t); //2种方法 //通过类名调用 Test::World(); //类似World() //对象调用 t.World(); //World() cout << sizeof(t) << endl; Test s; s.n = 20; s.World(); }
Hello n:0 World n:0 World n:0 1 World n:20
- 实例3:static属于类,不属于对象
下面代码输出为()
#includeusing namespace std; class Test{ public: static char x; }; char Test::x='a'; int main(){ Test exp1,exp2; cout< A. f a
B. a f
C. a a
D. f f
- 答案:
a f
- 实例4:
#includeusing namespace std; class Object{ public: Object():id(count++){ ++live; } ~Object(){ --live; } static int GetCount(){ return count; } static int GetLive(){ return live; } void Print()const { cout << id << ":" << this << endl; } private: const int id; //注意int和const int时候构造函数的区别 static int count; static int live; }; int Object::count = 0; int Object::live = 0; int main(){ Object o1; Object o2; Object o3; Object o4; Object(); //匿名对象,构造完即析构 Object(); Object(); Object(); o1.Print(); o2.Print(); o3.Print(); o4.Print(); cout << Object::GetCount() << endl; //累计构造8个对象 cout << Object::GetLive() << endl; //析构4个,还剩4个 cout << sizeof(o1) << endl; cout << sizeof(Object) << endl; } 0:0x7ffe1b7759fc 1:0x7ffe1b7759f8 2:0x7ffe1b7759f4 3:0x7ffe1b7759f0 8 4 4 42.10 const static限定符1、static const与const static修饰变量的效果一样
2、属于类,不属于对象
3、不能在构造函数中初始化
实例:#includeusing namespace std; class StaticConstTest{ public: void print(){ cout << test1 << " " << test2 << endl; } private: static const int test1 = 1; static const int test2; }; const int StaticConstTest::test2 = 2; int main(){ StaticConstTest sct; sct.print(); }
- cosnt、static、const static对比
变量类型 声明位置 一般成员变量 在构造函数初始化列表中初始化 const成员常量 必须在构造函数初始化列表中初始化 static成员变量 必须在类外初始化 static const/const static成员变量 变量声明处或者类外初始化 注:static const/const static成员变量在类初始化必须是数字类型
2.11 内联函数inline – 宏定义的接班人
2.12 运算符重载
- 条件
一般用在代码比较简单的函数- 语法
1、关键字inline必须与函数实现/定义体放在一起才能使函数成为内联,将inline放在函数声明前面不起任何作用
2、定义在类声明之中的成员函数将自动地成为内联函数
3、通常内联函数定义在头文件中。- 慎用内联
1、如果函数体内的代码比较长,使用内联将导致内存消耗代价较高
2、如果函数体内出现循环,那么执行函数体内代码的时间要比函数调用的开销大
3、不要随便地将构造函数和析构函数的定义体放在类声明中- 本质
内联函数的代码直接替换函数调用,省去函数调用的开销
- 运算符重载主要有两种方式实现:
1、成员函数运算符重载
返回值类型 operator 运算符(参数){ 函数体 }2、友元函数运算符重载
friend 返回值类型 operator 运算符(形参列表) { 函数体 }
No. 类型 运算符 成员函数 友元函数 1 双目算术运算符 + - * / % 类名 operator 运算符(const 类名&) const 类名 operator 运算符(const 类名&, const 类名&) 2 关系运算符 == != > >= < <= bool operator 运算符 (const 类名& ) const bool operator 运算符 (const 类名&,const 类名&) 3 双目逻辑运算符 && ¦¦ bool operator 运算符 (const 类名& ) const bool operator 运算符 (const 类名&,const 类名&) 4 单目逻辑运算符 ! bool operator !() const bool operator ! (const 类名&) 5 单目算术运算符 + - 类名 operator 运算符 () 类名 operator 运算符 (const 类名&) 6 双目位运算符 & ¦ 类名 operator 运算符 (const 类名& ) const 类名 operator 运算符 (const 类名& ,const 类名& ) 7 单目位运算符 ~ 类名 operator ~ () 类名 operator ~ (类名&) 8 位移运算符 << >> 类名 operator 运算符 (int i) const 类名 operator 运算符 (const 类名&,int i) 9 前缀自增减运算符 ++ – 类名 operator 操作符 () 类名 operator 操作符 (类名&) 10 后缀自增减运算符 ++ – 类名 operator ++ (int) 类名 operator ++ (类名&,int) 11 复合赋值运算符 += -= *= /= %= &= ¦= ^= 类名& operator 运算符 (const 类名& ) 类名& operator += (类名&,const 类名&) 12 内存运算符 new delete 参见说明 参见说明 13 流运算符 >> << - 参见说明 14 类型转换符 数据类型 参见说明 - 15 其他运算符重载 = [] () -> 参见说明 -
- 实例1:
#include#include using namespace std; class String { public: //构造函数 String(const char* str) { if(NULL == str) { this->str = NULL; size = 0; } else { size = strlen(str)+1; this->str = new char[size]; if(NULL == this->str) return; strcpy(this->str,str); } } //拷贝构造函数 String(const String& s) { if(NULL == s.str) { this->str = NULL; size = 0; } else { size = s.size; str = new char[size]; if(NULL == str) return; strcpy(str,s.str); } } //赋值运算符重载 String& operator=(const String& s) { if(this == &s) return *this; //自我赋值判断 if(NULL == s.str) { delete [] str; str = NULL; size = 0; } else { size = s.size; char* tmp = new char[size]; if(NULL == tmp) return *this; //释放原有内存 delete [] str; str = tmp; strcpy(str,s.str); } return *this; } String operator+(const String& s)const { char str[size+s.size+1] = {0}; strcpy(str,this->str); strcat(str,s.str); return String(str); } bool operator==(const String& s)const { return strcmp(str,s.str) == 0; } bool operator!=(const String& s)const { //return strcmp(str,s.str) != 0; return !(*this == s); //调用上面定义的==判断两个对象是否相等 } friend ostream& operator<<(ostream& os,const String& s); friend istream& operator>>(istream& is,const String& s); //析构函数 ~String() { delete [] str; str = NULL; size = 0; } size_t GetSize() { return size; } char operator[](int i)const { return str[i]; } char Get(int i)const { return str[i]; } void Print() { cout << str << endl; } private: char* str; size_t size; }; ostream& operator<<(ostream& os,const String& s) { return os << s.str; //等价 //os << s.str; //return os; } istream& operator>>(istream& is,const String& s) { // is >> s.str; return is; } int main() { String s("abc"); //调用构造 s.Print(); String t = s; //初始化创建对象(从无到有)调用拷贝构造 t = s; //赋值 两个对象都存在 调用赋值运算符重载 t.Print(); t = t; //自我赋值 s.Print(); String n(NULL); String m = n; m = n; m.operator=(n); //等价m = n; const char* str = "def"; String w = s+s+str+"123"; w.Print(); for(int i = 0; i < s.GetSize(); ++i) { //cout << s[i]; //s[i]=>s.operator[](i) cout << s.Get(i) << " "; } cout << endl; cout << w << endl; cout << (s == w) << "t" << (s != w) << endl; String w2 = s+s+str+"123"; cout << (w == w2) << "t" << (w != w2) << endl; } abc abc abc abcabcdef123 a b c abcabcdef123 0 1 1 0
- 实例2:
#includeusing namespace std; class Float { public: Float(float value):value(value) {} void Print() { cout << this->value << endl; } friend Float operator+(const Float& a,const Float& b); friend bool operator==(const Float& a,const Float& b); friend bool operator!=(const Float& a,const Float& b); friend istream& operator>>(istream& is,Float& f); friend ostream& operator<<(ostream& os,Float& f); private: float value; }; //友元函数写法 bool operator==(const Float& a,const Float& b) { return abs(a.value - b.value) < 0.000001; } bool operator!=(const Float& a,const Float& b) { return !abs(a == b); //使用等于运算符重载函数判断 } ostream& operator<<(ostream& os,Float& f) { os << f.value; return os; } istream& operator>>(istream& is,Float& f) { is >> f.value; return is; } Float operator+(const Float& a,const Float& b) { return Float(a.value + b.value); } int main() { Float f1(3.14); //Float f1 = 3.14; // 等价上面,过程类似于:Float f1 = Float(3.14); f1.Print(); //成员函数f3 = f1.operator+(1); //友元函数f3 = operator+(f1,1); Float f2(1.3); // Float f3 = f1 + 1.3; // 成员函数可以用,友元函数可以用 Float f3 = 1.3 + f1; // 成员函数不可以用,友元函数可以用 f3.Print(); Float f4(3.14); cout << (f1 == f4) << endl; cout << (f3 == f4) << endl; cout << (3.14 == f4) << endl; cout << (f4 == 3.14) << endl; //f1.operator==(f2); // 成员函数判断 //operator==(f1,f2); // 友元函数判断 cout << f1 << " " << f2 << endl; //等价过程: //operator<<(cout,f1); //cout << " "; //operator<<(cout,f2); //cout << endl; cin >> f1; f1.Print(); } 3.14 4.44 1 0 1 1 3.14 1.3 3.1415 3.1415



