c++程序设计
数据类型与语句
数据类型
类型 | 位 | 范围 |
---|---|---|
char | 1 个字节 | -128 到 127 或者 0 到 255 |
unsigned char | 1 个字节 | 0 到 255 |
signed char | 1 个字节 | -128 到 127 |
int | 4 个字节 | -2147483648 到 2147483647 |
unsigned int | 4 个字节 | 0 到 4294967295 |
signed int | 4 个字节 | -2147483648 到 2147483647 |
short int | 2 个字节 | -32768 到 32767 |
unsigned short int | 2 个字节 | 0 到 65,535 |
signed short int | 2 个字节 | -32768 到 32767 |
long int | 8 个字节 | -9,223,372,036,854,775,808 到 9,223,372,036,854,775,807 |
signed long int | 8 个字节 | -9,223,372,036,854,775,808 到 9,223,372,036,854,775,807 |
unsigned long int | 8 个字节 | 0 到 18,446,744,073,709,551,615 |
float | 4 个字节 | 精度型占4个字节(32位)内存空间,+/- 3.4e +/- 38 (~7 个数字) |
double | 8 个字节 | 双精度型占8 个字节(64位)内存空间,+/- 1.7e +/- 308 (~15 个数字) |
long long | 8 个字节 | 双精度型占8 个字节(64位)内存空间,表示 -9,223,372,036,854,775,807 到 9,223,372,036,854,775,807 的范围 |
long double | 16 个字节 | 长双精度型 16 个字节(128位)内存空间,可提供18-19位有效数字。 |
sizeof()
运算符
sizeof(<类型>)
或者sizeof(<变量>)
1 | int a; sizeof(a) |
类型转换
C++的转换语法不像Java那么严格,例如,double可以自动(隐式)转换出float,浮点数自动转换得到整数,但自由的代价是风险增大
1 | int x = 1.7;//居然可以 |
C++支持的强制类型转化语法
Java风格,C风格 (类型名)(表达式):
(double)a
,(int)(x+y)
,(int)6.2%4
C++风格 类型作为函数名,()作为参数:
double(a)
,int(x+y)
,int(6.2)%4
复杂风格,引入注目,突出这儿有类型转换:static_cast <type-id>(expression)
1 | int ii = static_cast<int>(5.5)://等效于 int ii = 5.5;等效于 imt ii = int(5.5); |
在强制类型运算后原变量不变,但得到一个所需类型的中间变量。
函数
函数概述
C/C++系统函数是C/C++编译系统(开发包)已预定义的函数。
- 包括一些常用数学计算函数(如sqrt0、exp0等)、字符串处理函数、标准输入输出函数、容器(List/Map)等
- 当用户使用库函数时,在程序中必须包含相应的头文件。
#include <iostream>
#include <iomanip>
- C++标准库万能函数头,懒人用,竞赛用,
#include <bits/stdc++.h>
定义和调用
函数的参数
- 形参是定义函数时的参数声明;
- 实参是运行函数时的参数赋值。
- 形参与实参必须类型相同一一对应。
- 实参必需是确定的数值,可以是由常量、变量或表达式/函数计舞产生的。
在C++中,函数的参数计算顺序是未定义的。
这意味着在表达式
max(f(x), g(x))
中,f(x)
和g(x)
的计算顺序是不确定的。函数声明:在—个文件(或者函数) a中声明函数b, 写出b 的函数头。
- 声明后, b在a 中可见
- 声明时,函数头形参只要类型,不要名字
- 函数定义的同时也是一次声明,可以多处声明
- 同一个cpp代码文件中,定义在后面的函数如果希望被前面的函数调用,必须声明
C++中, 所有函数的定义都是独立的,不可嵌套定义。(Java可以定义匿名内类, C/C++不可以)
内联函数
内联函数(inline) , 编译时,就将被调函数体的代码直接插到调用处(c/c++特色)
- 实质是用目标程序的长度来换取执行时间。程序代码占用多份空间,但免去调用的额外开销。
- —般把规模很小但频繁调用的函数声明成内联
- 内联函数的定义方法: 在函数类型前增加修饰词inline 。
1 | inline int max (int x, int y) |
- 使用inline , 只是请求编译器内联该函数,是否内联由编译器决定。
函数的重载
重载函数是相同的作用域,两个或更多具有相同函数名,不同参数列表的函数。编译器根据调用时的实参个数以及类型来确定调用淮。
重载函数必须具有不同参数列表,理解为:不同的参数个数,或不同的参数类型。
形参缺省值与重载有些类似,注意区分
int func(int xl, int x2 = 2, int x3 = 3) { return xl + x2 + x3; }// int void main(void){ cont<< func(10) <<endl; // 10, 2,3 cout<< func(10,8} <<endl; //10,8,3 1
1
2
3
4
5
- 定义时不可以靠左边缺省
```c++
int func(int x1 = 5, int x2, int x3) //错误赋值时不可以中间缺省
1
cout<< func(lO, , 8) <<endl; II 错误
不可以定义与声明同时赋值
如果一个函数有原型声明,则默认参数值应在函数原型声明中给出
1
2int func(int X 1 , int x2 = 3, int x3=4); //声明
int func(int xl, int x2, int x3) { return xl + x2 + x3;}// 定义
当形参缺省多处声明+重载相遇
1
2
3
4
5
6
7
8 int f(int x,int y=3); int f(int);
int main(){
int f(int x,int y=4); //如果去掉,则因为二义性导致编译错误
cont<< f(3) << endl; //结果7
return O;
}
int f(int x,int y){ return x+y;} //重载
int f(int x){ return x;} //重载
模板函数
以类型作为变量
- 定义T是一个变量, 变量T代表某一个类型,而不是数值。
在编译时, 可以允许编译器根据max的实参类型,推断T, 也可以指定T 的类型。
1 | template<typename T>//作用域就是下面的一个方法 |
- 函数模板(function template) 是—个通用函数,其函数返回类型、形参类型、以及函数体内的局部变量定义的类型,都可以不具体指定,用虚拟类型(如T1 ,T2) 代表。
- 作用:函数实现逻辑相同的函数,只有变量类型不同的函数,都可以用统一模板来代替,不必定义多个函数。
- 在编译过程中,如果不指定具体类型,编译器通常会根据函数的实参类型来取代模板中的虚拟类型, 创建(实例化) 具体函数;
- 虽然你写了—个函数模板max, 实际在你的代码中有多个max
- 但虚拟返回值类型不能推断。
- 函数模板不能取代函数重载,难以处理参数个数不同,或逻辑实现不同(实数、复数的加法)的情况。
作用域和生命周期
变量具有生命周期和作用域。函数具有作用域。
生命周期指变量的空间分配到释放的周期,三类生命周期,为方便管理,C++将不同生命周期的变量存储在不同性质的存储空间中。
- 1 、静态:一旦分配,一直存在,直到程序结束被释放;
- 2、栈:函数开始,分配空间(压栈),函数结束,被释放(弹栈);
- 3 、堆:程序员主动申谓释放; new申请, delete释放(C语言 malloc 申请, free 释放);
存储类别
- 程序代码区—存放函数体的二进制代码(忽略)
- 静态区(static) : —旦分配, 直到程序结束才回收(终生不死);全局变量,静态局部变量(包含文字常量区,存放字符串常量)
- 栈(Stack) : 存储函数参数和局部变量,空间由系统自动动态分配、回收。函数被调用、分配、压栈;执行完回收、弹出。特点:空间小(×MB量级),速度快
- 堆(Heap) : 程序员动态申请的内存块, new申请,delete释放(Java自动释放)。特点:空间大(×GB量级),速度慢。对于C/C++ , 如果程序员忘记回收,终生不死,直到操作系统关闭。
静态局部变量:作用范围局部(函数内部),生命周期:静态。
- 只最初赋值一次,在静态存储区内分配存储单元,在程序整个运行期间都不释放。在下一次该函数调用时,该变量保留上—次函数调用结束时的值。
- 静态局部变量或全局变量未赋初值时, 系统在编译时自动使之为0或空字符。
- 静态局部变量—直存在,但对其他函数”不可见”。可见范围仍是局部
- 用静态存储要多占内存,而且降低了程序的可读性。明明看到
static int i =3
; 但i 其实不—定是3 , i只是最初是3 。 - 如不是必须,不要多用静态局部变呈。
作用域
局部作用域:在函数内部或在块(复合语句)中定义的变量都是局部变量,其作用域开始于声明处,结束于函数/块的结尾处。
- 不同的函数可以使用相同名字的局部变量,同名局部变已分属不同函数,互不干扰。
- 局部变量名相同时,以最内层定义的局部变量为准。java 中,不允许。C++, 尽量避免
全局作用域(物理文件)
在函数外定义的变量称为全局变量,对应文件作用域。其缺省的作用范围是: 从定义位置开始到该源程序文件结束。
全局变量与局部变量同名时,局部变量优先。可以利用域作用符::标记全局变量
1
2
3
4
5
6
7
8
9
10
11
12
int i = 100;
void main(void)
{
int i , j=50;
i = 18; //访问局部变量i
::i = ::i+4; //访问全局变量i
j = ::i + i ; //访问全局变量i和局部变量j
cout<<"::i="<<::i<<'\n';//::i=104
cout<<"i="<<i<<'\n';//i=18
cout<<"j="<<j<<'\n';//j=122
}函数要访问当前源文件中定义的全局变量:
通常在前面定义全局变量,后面定义函数,函数可以访问全局变量
如果在后面定义全局变量,需要在前面利用extern声明全局变量,声明之后可以使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
using namespace std;
int max(int,int); //函数声明
void main()
{
extern int a,b;//对全局变量a,b作提前引用声明,不是定义,不是定义,不是定义
cout< < max(a,b) <<end I;
}
int a=15 , b=-7;//定义全局变量a,b
int max(int x,int y)
{
int z;
z=x>y?x:y;
return z;
}声明可以在函数内部或者外部,声明的位置决定了其作用范围
static修饰函数
static 也可以修饰函数,描述其为内部函数
内部函数:函数只局限于在本文件中调用,其它文件不能调用,用static 定义该函数。
外部函数:是函数的默认形式,即可以被外部调用。
在调用文件中用extern 声明,把该函数的作用域扩展到调用文件。
1
2
3
4static float fac1(float n)
{……} //该函数不能被其他文件使用
float fac(int n) {}
//该函数可以被其他文件使用1
2extern float fac(int n);
//这个函数的定义来自其他文件
命名空间作用域
c++提供了名空间(namespace) 机制来解决名冲突问题。
在—个名空间中定义的全局标识符(函数/全局变呈),其作用域为该名空间。
当在—个名空间外部需要使用该名空间中定义的全局标识符时,可用该名空间的名字来修饰或受 限。
1 | namespace A |
1 、不使用using
1 | ...A::x... //A中的x |
2、使用using省略整个命名空间
1 | using namespace A; |
3、使用using省略命名空间的某个函数
1 | using A::f; |
预处理编译
文件包含
一个源文件也可以将另外一个源文件的全部内容包含进来,即将另外的文件包含到本文件之中。
#include "文件名“
通常情况下, 很少在一个cpp中include另一个cpp , 而应当include其声明文件h , 即包含file的声明(引用),而不是其全部。(可以使用,但不拥有)
mathx.h
1 | const double Pl = 3.14; |
mathx.cpp
1 |
|
file1.cpp
1 |
|
.h文件中能包含:
- 类成员数据的声明,但不能赋值
- 类静态数据成员的定义和赋值,但不建议,只是个声明就好。
- 类的成员函数的声明
- 非类成员函数的声明
- 常数的定义:如:constint a=5;
- 静态函数的定义
- 类的内联函数的定义
不能包含:
- 1. 所有非静态变量(不是类的数据成员)的声明
- 2。 默认命名空间声明不要放在头文件,using namespace std;等应放在.cpp中,在 .h 文件中使用 std::string
因为一个头文件的内容实际上是会被引入到多个不同的 .cpp 文件中的,并且它们都会被编译。放声明当然没事,如果放了定义,那么也就相当于在多个文件中出现了对于一个符号(变量或函数)的定义,纵然这些定义都是相同的,但对于编译器来说,这样做不合法。
数组与容器
一维数组
数组是同一类型的一组值(例如, 10个int或15个char) , 在内存中顺序存放。
整个数组共用—个名字,其中的每—项称为—个元素,对应一个下标,下标从0开始。
C++规定,数组元素个数必须是常量,不能通过赋值修改数组长度。(VC 会报错,但MinGW 允许)
C++ 中, 数组不是对象,不用new去创建
- 越界后果自负。
int a [10]; a [15] = 1 ;
可能带病工作,可能立即崩溃 - 定义的同时必须确定长度。
- 在栈上分配空间,由于栈空间有限, 不要定义很大的数组。
- 如果需要很大的数组( 1 个亿), 需要用指针配合new 申请堆空间。
定义数组时,可以用{}给数组元素赋值而不指定长度
赋值元素的个数为数组的长度。int a[]= {0,1, 2, 3}; 则a的长度为4 。
定义时,同时指定长度与{ }赋值,应该保证指定长度>=赋值元素个数
未赋值的元素为0 、0.0或者null 。int b[10]= {1};
int c[2]= {1, 2, 3}; 挑衅编译器,规范的编译器捉示语法错;
不赋初值时, static 或全局数组均默认其为0或’\0 ‘ , 其他局部数组赋值随机
用数组名作函数参数
在C++中,数组名被认为是数组在内存中存放的首地址。Java中也是这样认为的
用数组名作函数参数。实参传递的是数组在内存中的地址
被调函数内外,共用同一段内存被调函数内可以修改数组的内容
用数组名作函数参数,实参/形参类型一致。
如果是一维数组,形参通常省略元素个数,例如int a[]
如果是二维数组,形参通常省略第一维,但不可省略第二维,且第二维必须与实参中的维数相等。
如果是高维数组,形参通常省略第一维,但不可省略低维,其他维度必须一致
字符数组与字符串
约定:用可作为字符串的结束标志,它占内存空间,但不计入串长度。C程序依据’\0’ 判断字符串是否结束,而不是根据字符数组的长度。
1 | char a[]= {'C','H','I','N','A'}; |
a是字符数组,非字符串; b是字符数组也是字符串,数组长度6B, 字符串长度5B
c是字符数组也是字符串,数组长度为10, 串长度为5, 串占有6个字节
字符串处理
要求有足够空间可以存放运笢结果
1 |
|
vector
包含头文件#include<vector>
标准操作:初始化、添加、遍历、插入、删除、查找、排序、释放(动态数据管理)
vector 的初始化:可以有五种方式
vector< int> a(10);
//10个整型元素的向最,初始长度,可变长vector< int> a(10, 1);
//10个整型元素的向鼠,且初值均为1vector< int> b(a);
//用b 向量来创建a 向量,整体复制性赋值vector< int> a(b.begin(),b.begin()+ 3);
//从容器b中获取第0个到第2个(共3个)元素int b[7] ={1,2,3,4,5,9,8}; vector< int> a(b,b+ 7);
//从数组b中获得初值
添加,插入,删除: 对千连续空间的数据结构,插入,删除代价巨大
vector< int> vec1; for(int i =O;i < 1 O;i + +) vec1 .push_back(i);
//后端插入vec1.insert(vec1.end(),5,3);
//从vec1.back位置插入5个值为3 的元素vec1 .erase(vec1.begin(),vec1.begin()+3);
//删除之间的元素,其他元素前移
遍历:
for(int i =O;i < =v.size()-1 ;i + +) cout < <v[i] < < " "
for (const int i:v)cout < < i < < "\t";
排序,查找:需要include<algorithm>
1 | int a[10] = { 9, 0, 1, 2, 3, 7, 4, 5, 100, 10 }; |
1 | bool cmp(const student &a, const student &b){ |
指针
指针变量的定义(语法)
类型标识符*指针变量名
1 | int * p; // *用于定义指针p, int定义指针p指向整数 |
- 定义语句中, * 表示定义指针;非定义语句中I *表示间接访问(指针指向的空间)运算。
- 定义语句中,&表示定义引用(后面讲) I 非定义语句中,&表示“取变星的地址”运算。
- 一个指针变星只能指向同一类型的变量。例如: int *p, 则p只能存放int数据的地址。
1 | void * p; //无类型指针、空类型指针、万能指针,只考虑指向,不考虑类型 |
空指针
空类型指针可以指向任何带类型的指针(地址)。
void * p= &x;
x可以是基本数据类型,或者对象/结构体类型;
p = &table; p = &book;
空指针在使用(获取内容)时必须转换成具体的类型,一般是原本的类型
1 | int a=3; |
指针访问数组
数组在内存中占据—段连续的单元,通过首地址+偏移可以访问到每个元素。
C++规定: 数组名就是数组的起始地址,同时也是指针,也是0号元素的地址;
实际应用中,通常额外引入一个新的指针变量p, 通过++,–在数组上游走
例如:
1 | int a[10],*p; |
可以通过p++指向下一个整数,但不可以a++因为数组名a是常量指针,只能指向数组首地址
C++ 规定,可以通过指针引用数组元素, 即a [i]
可以理解为*(a+i)
。
const指针
1 | const int pi=3; |
引用
如果&a 的前面有类型符, 则是对引用的声明/定义;
int &a
如果&a 的前面无类型符,则是取变量的地址运算。
int a, * p; p = &a;
- 一个变量可以有多个引用(别名)
- 理解为c++向java学习就行了,把引用的传参逻辑当作java的传参
作用
用作函数的(形式)参数,实现参数的双向传递。
1 | void swap(int &x, int &y) |
引用作为形参,实参是变呈而不是地址,这与指针变呈作形参不一样
但实际效果—样,习惯上,双向传递一个参数,使用引用,传递数组,使用指针(数组首地址)
引用可以部分代替指针的操作,例如双向传递参数,降低程序设计的难度
引用更符合数据结构代码风格
常量引用:用canst修饰的引用,防止通过引用修改数值
1 | void m3(const int& x){ ~ } // 传递常引用,通过x修改数值, 语法错误 |
自定义数据类型
类和对象的使用
构造函数
在创建对象时,负责将对象初始化(成员变量赋初值)。
任何类都会有构造函数,无论你是否主动定义
没有主动定义的情况,编译器自动产生一个缺省构造函数className::classNameO { }
,对所有数据成员置0, 或者NULL
主动显式的定义了类的(任何)构造函数,编译器不再产生缺省构造函数。
但可以使用” = default “ 强制编译器生成缺省构造函数
1
2 Clock() = default;//指示编译器提供默认构造函数
Clock(int newH, int newM, int newS); //构造函数
参数初始化表
在函数头部对数据成员初始化。
1 | Box::Box(int h,int w,int len) : height(h), width(w), length(len){} |
其中, height; width; length是成员变量;
相当于
1 | Box::Box(int h,int w,int len) {height=h; width=w; length=len;} |
委托构造函数
构造函数调用其他构造函数
1 | Clock(): Clock(0, 0, 0) {}//委托构造函数(delegat i ng constructor) |
析构函数
析构函数在对象的生命期结束时,被系统自动调用,作用是收回为对象分配的存储空间。
析构函数名必须是在类名前面加上字符”~” 。
1 | ClassNaine::~ClassName( ){ …… } |
析构函数不能带有任何参数,没有返回值,不指定函数类型。
如果没有显式定义析构函数,则编译器自动产生一个缺省的析构函数, 其函数体为空。
分配空间时,构造;回收空间时,析构
- 全局对象(在函数外定义的对象),在程序开始执行时,调用构造函数;到程序结束时,调用析构函数。
- 局部对象(在函数内定义的对象)当程序执行到定义对象的地方时,调用构造函数;在退出对象的作用域时,调用析构函数。
- 静态变量(static定义的局部对象),在首次到达对象的定义时调用构造函数;程序结束时,调用析构函数
- 用new运算符动态生成的对象,在new产生对象时调用构造函数,在使用delete运算符释放对象时,调用析构函数。系统不能自动释放动态生成(new)的对象。
如果在构造函数中用new为对象动态分配了存储空间,则在类中应该定义一个析构函数,使用delete,收回由new申请的存储空间。
复制构造函数
编译器自动生成的复制构造函数,用初始值对象的每个成员数据的值
1 | A::A(const A &a){ |
C++11: 用”= delete” 指示编译器不要生成默认的复制构造函数。
1 | class Point{//Point 类的定义 |
缺省的复制函数为浅拷贝,如果数据成员是指针,则复制函数拷贝指针,不拷贝指针指向的内存空间。推论:使用new 申谓空间, 通常需要自定义复制函数,实现深拷贝
1 | class Str{ |
复制构造函数被自动调用的三种情况
定义—个对象时,以本类另—个对象作为初始值,发生复制构造; A a2 = a1;//Aa2(a1)
如果函数的形参是类的对象,调用函数时,将使用实参对象初始化形参对象,发生复制构造;
int func(A a); A a(1,2); func(a);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
- 如果函数的返回值是类的对象,函数执行完成返回主调函数时,将使用return语句中的对象初始化—个临时无名对象,传递给主调函数,此时发生复制构造。
- ```cpp
Box f() //函数f的类型为Box类类型
{
Box box1 (12, 15, 18) ;
return box1; / /返回值是Box类的对象
}
int main()
{
Box box2; //定义Box类的对象box2
box2=f() ; //返回Box类的临时对象,并将它赋值给box2
}
复制构造函数被自动调用的三种情况
定义—个对象时,以本类另—个对象作为初始值,发生复制构造;
Aa2 = a1;//Aa2(a1)
1
2
3
4
5
- -如果函数的形参是类的对象,调用函数时,将使用实参对象初始化形参对象,发生复制构造;
- ```cpp
int func(A a); A a(1,2); func(a);
如果函数的返回值是类的对象,函数执行完成返回主调函数时,将使用return语句中的对象初始化—个临时无名对象,传递给主调函数,此时发生复制构造。
Box f(){//函数f的类型为Box类类型 Box box1(12,15,18); return box1;//返回值是Box类的对象 } int main(){ Box box2;//定义Box类的对象box2 box2=f();//返回Box类的临时对象,并将它赋值给box2 }
1
2
3
4
5
6
7
8
9
10
### 返回对象引用和返回对象的区别
```c++
Box funl ()//返回一个克隆对象
Box & fun(const Box & b)//返回引用(对象的名字)
返回引用,调用和被调函数对同一个对象进行操作,节省时间和内存。
函数不能返回在函数中创建的局部变量对象的引用, 因为当函数结束时,局部变量对象将消失,这种引用是非法的。(在这种情况下,通过new动态创建)
静态成员
静态成员被该类的所有对象所共有、共享。用关键字static声明
在内存中只占一份空间。如果改变它的值,则各对象中这个数据成员的值都同时改变。
只要在类中定义了静态数据成员,即使不创建对象,也为静态数据成员分配空间,可以通过类名访间到类的静态成员。
全生命周期,在程序启动时被分配空间,到程序结束时释放;不随对象的建立而分配空间,也不随对象的撤销而释放。
成员函数
1 | static int volume(); |
在类外调用静态成员函数, 可以用类名或者对象名。如
1 | Box::volume(); |
静态成员函数可以访问本类中的静态数据成员,不能访问本类中的非静态成员。
友元
友元将—个模块(函数、类)声明为本类的朋友,从而可以访问本类中隐截内容。
友元函数
外部函数f () , 在类A中用friend修饰声明,则f()为类A的友元函数, f可以访问A的所有成员。
friend void f () ;
友元函数不一定是独立函数,也可以是另外一个类的函数
友元类
在类A中声明另—个类B声明为friend , 则类A所有数据类B可以访问: friend class B;
友元的关系是单向的而不是双向的,友元关系不能传递
作用:增加灵活性,使程序员可以在数据安全性和使用便利性之间做选择。指明哪些是朋友,对淮开放。
副作用: 破坏数据封装和隐截。建议尽呈不要使用友元类。
副作用2 : 程序设计更加复杂了,—些额外的问题,例如父亲的朋友是不是朋友
const
const修饰的变量称为常量。
const修饰的成员数据称为常数据成员
只能通过构造函数的参数初始化表对常数据成员进行初始化
const int hour; //声明hour为常数据成员 Time::Time (int h):hour(h){ hour = h; //错误}
1
2
3
4
5
6
7
8
9
- const修饰的成员函数称为常函数
- 常成员函数可以引用非const的数据成员,但不能修改其数值
- ```cpp
void get_ time() canst; //在声明和定义函数时者都要有const关键字,在调用时不必加const
const声明的对象为常对象。
常对象必须进行初始化,不能被更新(修改)。
对象的数据成员的值不被改变
const Time tl(l2,34,46); //tl是常对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
- 对象的数据成员的值不被改变
Time canst tl (12, 34, 46); / /tl是常对象
常对象必须进行初始化,不能被更新(修改)。
## 运算符重载
运算符重载:为运算符(+_*/><...)定义函数(运算符重载函数一个(若干个)
遇到运算符根据不同的运算对象调用对应的函数
<返回类型> operator <运算符>(<参数表>)
operator是运算符重载函数关键字,operator<运算符>是函数名
运算符的操作数映射为重载函数的参数,运算符重载函数通常是类的成员困数或者是友元函数。
**重载运算符+**
```cpp
Class A{
int i;
public:
A(int a=0){i=a;}
A operator+(const A &a){//重载运算符+
A t;
t.i=i+a.i;
return t;
};
重载运算符+=
+=重载函数,其返回值类型为void。因为+=-=*=等修正运算,不会产生新的对象
1 | void operator+=(const A &a){//重载运算符+= |
自增运算符
1 | A& operator++() { // 重载前置自增运算符 |
运算符重载为成员函数的参数只能有二种情况:没有参数或带有—个参数。
对于单目运算符(如++),正载时通常不能有参数;
对于双目运算符,只能带有—个参数。参数可以是对象,对象的引用,或其它类型的参数。
只能对C++ 中已定义了的运筒符进行重载。但有5个不允许重载
.
(点运算符)s.length
,但是->
(箭头运算符)是可以重载的- 域运算符,
Math::P
.*
(点星运算符,)s.*p
,即成员是指针类型?:
(条件运算符)sizeof
,不可以重载
禁止重载(修改)基本类型数据的运算符。参数不能全部是C++的基本标准类型。
静态成员函数不能是运算符重载函数
运算符重载为类成员函数, 成员函数所属对象是一个操作数,另一个操作数是函数参数
1 | A a ,b, c; |
运算符重载为类的友元函数(或者普通函数), 参与运算的对象全部成为函数参数。
1 | c=a+b; //实际上是c=operator+(a, b); 两个参数friend A operator+ (A &a, A &b) |
如果参数有基本数据类型时,则运算符建议重载为友元函数。例如:
1 | Complex Complex::operator+(int &i){ |
则限制使用者必须写作c3 = c2+100;
而不能写成c3=100+c2; 因为100不是类对象,100.operator+(c2)编译错误
流运算符重载
1 |
|
赋值运算符重载
1 | A& A::operator =(A &b){//重载赋值运算符 |
注意
1 | A a2;//调用了默认构造函数 |
继承和派生
访问权限
1 | class SubClass: public/protected/private BaseClass |
公有派生,基类中所有成员在派生类中保持访问权限。
保护派生时,基类中公有成员在派生类中均变为保护的;
私有派生时,基类中所有成员在派生类中均变为私有的;
1 | class A{ |
构造函数
派生类的构造函数要么调用基类的构造函数,要么调用自己的另一个构造函数二选一。
派生类的构造函数调用不能出现递归
派生类如果有多个构造函数,必然有至少一个调用基类的构造函数
必须在初始化列表中调用
1 | class A{ |
构造的顺序
1.执行参数初始化列表
A)调用基类的构造函数,初始化基类成员
A(i+1)
B)按照成员变量定义顺序,依次初始化
int b;A a;
则b(i++),a(i)A a;int b;
则执行a(i),b(i++)
2.调用自身构造函数的实现部分
析构顺序相反
多重继承
1 | class D: public A, protected B, private C { |
二义性冲突
当派生类中新增加的数据或函数与基类中成员同名时,缺省优先调用派生类中的成员。
多个基类数据或函数同名时,利用类作用域符号来指明数据或函数的来源。
同一个公共的基类在派生类中产生多个拷贝,多占用空间,容易混淆。
ctass A{ public: int x; A(int a) { x=a;}}; class B: public A{ public: int y; B(int a=0,int b=0):A(a) { y=b;}}; class C: public A{ public: int z; C(int a,int c):A(a){ z=c; }}; class D: public B, public C{ public: int dx; D(int al,int b,int c,int d,int a2):B(al,b),C(a2,c) {dx=d;}}; void main(void){ D dl(l0,20,30,40,50); cout<<dl.x<<endl;//模糊引用错误 cout<<dl.B::x<<endl; //10 cout<<dl.C::x<<endl; //50 }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
### 虚基类
在多重派生的过程中,若使公共基类在派生类中只有一个拷贝,可将公共基类说明为虚基类
由虚基类派生出的对象初始化时, **直接调用**虚基类的构造函数。
规则:
1) —个类可以在—个类族中既被用作虚基类,也被用作非虚基类。
2) 在派生类的对象中,同名的虚基类只产生—个虚基类了对象,而某个非虚基类产生各自的子对象。
## 多态
调用同—个函数名,根据引用类型实现不同的功能。
与java相同,基类的指针可以指向子类(派生类)对象。
C++中,若要访问派生类中相同名字的函数,必须将基类中的同名函数定义为虚函数, 只有用virtual修饰的函数才是可以被覆盖的
```cpp
class Point{
float x,y;
public:
Point() {}
float area(void){
returm 0.0:
}
const float Pi=3.14159.
}
class Circle: public Point{//类Point的派生类
float radius;
public:
Circle(float r): radius(r){}
float area(void){
return Piradius radius.
}
void main(void){
Point *pp;
//基类指针
Circle c(5.4321):
PP-&c;//,可以将派生类对象的地址赋给基类指针
cout<<pp->area()<<endl;//点的面积,结果是0
cout<<c.area<<endl;//圆面积
}
1 | class A{ |
父类(基类)的析构方法一般为virtual, 保证派生类的析构方法得到执行
抽象类
在基类中不对虚函数给出实现, 在派生类中进行实现。这时基类中的虚函数只是一个入口,具体的操作由派生类中的对象实现。这种虚函数称为纯虚函数(对应Java抽象方法) 。
至少包含一个纯虚函数的类,称为抽象类(abstract c l asses) 。没有特殊关键字声明抽象类。l由象类只能作为基类,不能用来创建对象。C++ 支持多重继承,没有interface
在定义纯虚函数时,不能定义虚函数的实现部分。把函数名赋0, 本质上是将函数代码段为0, 即没有代码段实现。在没有通过派生类实现纯虚函数前,不能调用虚函数。
1 | class A{//抽象类 |
- 本文作者: NICK
- 本文链接: https://nicccce.github.io/TechNotes/Cpp-Note/
- 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!