C++复习
北京邮电大学期末复习考试资料
适用于北京邮电大学《C++程序设计语言》期末考试
C基础
- 面向过程的程序设计语言/结构化程序设计
- 结构化数据,结构化语句,数据抽象,过程抽象
- 面向过程的程序设计主要思想是自顶向下,逐步求精,模块化
C++基础
- 自底向上的分析,是从具体到抽象,因为oop(面向对象编程)的第一步是从对象抽象出类
- 自顶向下的分析,是从抽象到具体,从大概要处理数据的什么功能细致的规划每一个步骤
- 组成C++程序的是函数
- C++中cin和cout是预定义的对象,不属于C++
- 通过IDE编写c++源程序文件.cpp
- 源文件编译后变为二进制不可执行的文件.obj
- .obj文件通过连接将文件变为可执行的二进制文件.exe
- C语言是一种面向过程的程序设计语言,又被称为结构化程序设计
- 翻译程序分为3种:汇编程序,编译程序,解释程序
C++基础2
C++不允许初始化时连续赋值,但C可以
int a=b=c=1;//会报错
码制
- 八位二进制补码表示的范围:00000000为0,01111111为127,再加1到10000000为-128(负数的的补码为除符号位取反最后再加1),10000001为-127,所以八位二进制补码的范围为[-128,127]
数据类型
- 变量的数据类型定义了变量的取值范围和可以进行的操作
- C++中的class,enum,struct等关键字并不代表一个自定义数据类型,他们用来定义一个自定义数据类型
“0”,’\0’,”\0”,’0’的区别
- “0”代表一个字符串,所占内存空间大小为2,一个ascii码为48的0和一个ascii码为0的结束符组成的字符串
- ‘\0’代表ascii码为0的结束符
- “\0”代表两个ascii码为0的结束符组成的字符串
- ‘0’代表一个ascii码为48的字符
标识符不能包含空格和C++字符集中其他的特殊字符
基本算术运算符
/,只要有一个操作数是浮点数,则运算结果就是浮点数
%,只能对整数运算,结果的正负由被除数决定
(double)(26/5); //输出结果1.0
++符运算的位置
int x = 1; while (x++<=8) { if (x % 3) continue; cout << x; } //此处++在x和8比较完之后运行,所以x会以9进入循环 //结果:369
int a = 0; cout<<(a++&& ++a); cout << a;//1 cout << (a++ || a);//1 //每一个结果单独运行 //结果1是先判断a为0,&&不在进行后面的判断,所以只在判断完之后进行了一次++运算 //结果2是左侧判断a为0,然后a自增1,到判断右侧的时候,a为1,所以判断结果为1
所以推测后置++运算符的运算时间是跟其相连的变量进行任意一次操作后自增?
运算符的优先级
一元运算符
基础算术运算符
关系运算符
逻辑运算符
三目运算符?:
赋值运算符
逗号运算符
//no.1 int a=1,b=2.c=3,d=4; cout<c+d?a+b:c
关系运算符结果为0,即对于后面的三目运算符取c
控制输入
- cin(提取运算符)以空格和回车作为分割符,不以他们作为输入信息,会忽略但不会终止输入
- cin.get()一个一个字符的读取,可以设置读取到回车或者换行停止读取
- cin.get(字符数组名,接受字符数目)用来接收一行字符串,可以接收空格
- cin.getline()从输入流中提取字符串,,用于获取一行字符串,包含3个参数:
- 字符串的起始地址
- 一行中最多输入的字符个数
- 约定行输入结束字符
- 当第三个参数省略的时候,系统默认为’\n’
进阶版—–字符数组的输入和输出,对上面的补充
- 直接cin输入,接收键盘输入的整个字符串,遇到空格或回车结束
- 除了字符数组,其他类型的数组要输出数组元素的值,必须用循环语句一个元素一个元素的输出,而数组名只能代表数组的存储地址
- getline(cin,string对象名称)接收一个字符串,可以接收空格并输出,需包含#include
,只用于string
sizeof和strlen
sizeof()返回变量声明后所占的内存数,参数可以是数组,指针,类型,对象,函数
char *str1="absde"; char str2[]="absde"; char str3[8]={'a'}; char ss[] = "0123456789"; 输出: sizeof(str1)=4;//指针的大小,长整型 sizeof(str2)=6;//编译时分配的数组空间的大小 sizeof(str3)=8;//固定数组的大小 sizeof(ss)=11//字符串的大小,字符串长度+1 //函数--函数的返回类型所占的空间大小,函数的返回类型不能是void
3. strlen(char*c)计算字符串的实际长度,并不包括结束符
4. str.length()和str.size()用于求string类对象的成员函数
5. sizeof用于求得的字符串长度+1
6. 对于字符数组,strlen()和sizeof()求得结果相同
string与C++字符串数组
包含库函数
string是C++标准库的一个类,定义的string变量其实是这个类的对象
将字符串常量存放到string对象中时,只存放字符串本身,不包含’\0‘
//不必考虑string不包含'\0',对它用sizeof和strlen会怎样 string str; getline(cin, str); cout << sizeof(str) << endl; cout << str.length() << endl; //对string对象用sizeof会得到固定值28 //无法对string对象使用strlen,这是cstring库中直接对对字符串操作的函数 //取而代之是length()和size()
字符串、字符数组、字符指针
字符串
- 系统会为字符串自动添加\0作为结束符,即使已经手动在结尾添加“abc\0”,系统仍会在后面添加’\0’
- 字符数组名指代一种数据结构,这种数据结构就是数组
- 字符数组名是一个指针常量(即指向不能变),其本身不可进行自增自减运算,即不能修改,但是可以借助其做地址偏移访问数组中元素
- 系统会为字符串自动添加\0作为结束符,即使已经手动在结尾添加“abc\0”,系统仍会在后面添加’\0’
初始化字符数组:
首先分为两种方式:用双引号内的字符串常量赋值/用字符常量初始化字符数组
只能在定义的时候直接用字符串赋值
用双引号内的字符串初始化字符数组,可以省略大括号,系统自动在数组最后一个元素后补’\0’
用字符常量初始化字符数组,(字符数组长度未定)需要自己添加字符串结束符’\0’
不加的话,能够成功初始化,但是不能正常输出想输出的内容,vs报乱码
在字符数组长度已知的情况下可以不用添加结束符’\0’,因为系统会把后面所有的部分都补成’\0’(如果字符串长度小于数组长度,则只将字符串中的字符赋给数组中前面的元素,剩下的内存空间系统自动用’\0’填冲
可以直接对字符数组名cin,cout
在初始化之后不能对字符数组名更改或赋值,字符数组名是不可修改的左值
字符数组的赋值:一个字符一个字符的赋值/使用strcpy(字符数组1,字符数组2)
- 不能用赋值语句将一个字符串常量或字符数组直接给字符串赋值
- 字符指针
- 可以开辟一个新的内存去更改或重新赋值,但不能在原地址上通过cin修改其指向的值
- 即指向可以改,但是不能通过解引用(或cin)改变其指向的值
- 联系
- 两个字符数组名进行逻辑运算时,实际进行运算的是数组首地址
- 两个字符串指针进行逻辑运算时,实际进行运算的也是地址
- 指向相同字符串常量的指针所含的地址相等,是同一地址
- 字符指针的输出
- 直接输出指向某种类型的指针输出其地址值
- 输出指向字符串的指针时,会输出其内容
- 字符数组可直接cin,cout,但是不可放在赋值号左边,即为不可修改的左值
strcpy和memcpy
c的库函数
strcpy(字符数组1,字符串2或字符指针3或string) 其实本质上
char*strcpy(char* dest,const char* src);
strcpy只用于字符串复制,还会复制字符串的结束符,并且遇到被复制字符串的结束符’\0’才结束
memcpy提供了一般内存的复制,根据第三个参数决定复制的长度
本质上为
void* memecpy(void* dest,const void* src,size_t count);
指针数组与数组指针
char *member[10]={""};//指针数组,数组元素是某种类型的指针 int arr[10]={0}; int(*p)[10]=&arr;//数组指针,指向数组的指针,指向数组的大小要给出
iomanip类库
- 设置域宽,setw(),只对其后输出的第一个数据有效
- endl不占用setw中设置的域宽,但是’\n’则会占用
- 其他操作符一旦设置,对其后的所有输入/输出都产生影响
- setiosflags(ios::left)设置左对齐,默认是右对齐
- setfill(char c)设置其他字符作为填冲
- setprecision(int n)控制小数位数
循环语句结构的主要部分:循环控制条件,循环体,循环控制变量
枚举类型
- 第一个枚举类型成员的默认值为0,后续成员值依次加一
- 随意给枚举元素赋值,之后的值也是依次递加一
标识符作用域
- 以标识符起作用的范围划分:全局作用域/局部作用域(可以覆盖全局变量)
- 局部变量可以隐藏全局变量,如果在有同名全局变量和局部变量的情况下,可以使用域运算符::对全局变量访问
作用域表示符的功能是标识某个成员是属于哪个类的(包括成员函数和成员变量)(指出作用域的范围?)- 以标识符在程序中所处的位置来划分:块作用域/函数作用域/类作用域/文件作用域
内联函数
- 内连函数体不能包含循环语句、switch语句
- 内联函数要先定义、后调用,不能先声明内联函数原型,再调用、定义
- 编译时插入
递归函数
- 递归调用指:函数中有直接调用函数自己的语句或通过其它函数间接调用函数自己的语句
- 递归函数的组成:更简单参数的递归调用,递归调用结束条件
二维数组与指针
char s[3][10] = { {"abc"},{"def"},{"ghi"} }; for (int i = 1; i < 3; i++) { cout << &s[i][i]; } //输出结果为efi
对于二维字符数组,取地址输出,其实就跟一维字符数组里直接输出数组名,它输出整个字符串类似,所以这个直接顺着地址向后输出
注意字符数组和普通数组的输出不一样
指向一维数组的指针的间接引用结果仍然是地址,数组元素的地址
int a[][3] = { 1,2,3,4,5,6 }; int* p = &a[0][0]; int i = 1, j = 1; cout << a[i][j] << endl; cout << *(p + 3 * i + j) << endl; cout << *(&a[0][0] + 3 * i + j) << endl; cout << *(a[i] + j) << endl; //cout << p[i][j] << endl;报错信息为:下标要求数组或指针类型 //输出结果都为5 //第一种是用二维数组的方式访问 //第二种和第三种都是将二维数组当成一维数组的存储方式,直接按内存存储的方式加偏移量访问元素 //
动态申请内存
动态申请内存指针的引用
clock& hclk=* new clock; delete& hclk;动态申请内存
new
- 对象数组
- 动态申请对象数组需要使用无参构造函数,不能指定初始值
- 但是动态申请对象可以指定初值,也可不指定初值
- 对象数组既可以赋初值又可以赋值
- 指向对象数组的指针不一定必须指向数组的首元素,指针可以移动(p++)
- 对象数组
delete
- 空指针可以delete
拷贝构造函数
- 拷贝构造函数的形参某个对象的引用名
- 传值的方式会调用该类的拷贝构造函数,从而造成无穷递归的调用拷贝构造函数,因此拷贝构造函数的参数必须是一个引用
特殊成员函数
- 静态成员函数
- 加修饰符static
- 静态数据成员要在类外进行初始化
- 引用静态数据成员时,要在其名称前加<类名>和作用域运算符
- 静态成员函数
C++建立类族是通过类的继承实现的
类的基础函数
- 类的作用域是指类定义和对应的成员函数定义的范围,通俗的称为类的内部
- 析构函数
- 前面加~只是用于区分构造函数
- 析构函数名和类名完全一致是错的!
- 析构函数是在对象被撤销时由系统自动调用,执行一些清理动作,不一定是收回分配的内存空间
- 只负责清理新定义的成员,只清理堆区不清理栈区成员,如果没有特殊指针数据成员需要清理,可以使用由系统提供的默认析构函数
- 构造函数
- 一个类的构造函数必须包含对其子对象的初始化
- 无参构造函数包括默认构造函数和带全部默认参数值的函数
- 拷贝构造函数
- 当用一个对象去初始化同类的另一个对象时
- 如果某函数又一个参数是类a的对象,(不是类a对象的引用)那么该函数被调用时,类的拷贝构造函数被调用
- 如果函数的返回值是类的对象时,那么函数返回时,类的拷贝构造函数将被调用
多继承情况下的二义性
- 解决二义性可以使用虚基类
- 也可以使用作用域运算符
- 虚函数解决类的多态性,虚基类解决二义性
类与结构体
类中默认成员访问属性是private
对象成员的表示与结构变量成员表示相同,使用运算符.或->
友元函数
1. 所谓私有成员是指只有类中所提供的成员函数才能直接使用它们,任何类以外的函数对他们的访问都是非法的。(错误)私有成员还可以被友元访问
指针与数组名访问数组
- 数组名和下标 a[0],a[4]
- 指针和下标 pa[0],pa[4]
- 指针加偏移量的解引用 *(pa+1)
- 数组加偏移量的解引用 *(a+4)
- 指针自加后的间接引用,如*p++,采用这种方式会改变指针本身的值,即改变指针的指向,必要时需要对指针重新初始化
- 但是不允许使用数组名自加的间接引用来访问数组元素,如*a++,因为数组名是常数,所以不能通过自加改变自身
int a[] = { 1,2,3,4,5 }; int *p = a+1; cout << (++(*p)) << endl; cout << *(p++) << endl; cout << *p++ << endl; cout << *p << endl; /*输出结果为3334 第一个是p指向a[1],解引用为2再++为3,并且改变了a[1]的值,变为了3 (注意区分自增和+1) //错误理解:第二个是p先偏移指向a[2],解引用就为a[2]的值3 无论是*p++还是*(p++)都是先解引用再偏移 所以第二个是p仍指向a[1],只是此时a[1]为3,再偏移指向a[2] 第三个是p先解引用,为a[2]的值3,再偏移指向a[3] 所以第四个直接解引用为a[3]的值4 */
函数的调用
- 函数语句,常用于void类型的函数
- 函数表达式,有返回值的函数
- 函数参数,函数的嵌套调用
函数默认返回类型为int型,因为return默认返回1
函数重载
- 调用原理:编译器在编译期间根据实参决定选择调用哪一个函数
- 作用于同一作用域
函数重载与含有默认参数值的函数
- 关系:在某些情况下可能会产生二义性,即对于被调用的一个函数,含默认参数值的函数看来是调用自己 ,在函数重载看来,是调用另一个和自身同名的函数名,只是函数参数比一样的函数
- 大部分情况下二者无影响
同名覆盖
- 父类中定义一个(虚)函数,在子类中重新实现了这个函数,并且函数在子类和父类中具有相同的函数原型(函数名、参数列表)
函数重载与同名覆盖
- 函数重载是同一类中的不同方法,函数覆盖是不同类中的同一方法
- 一般情况下,同名覆盖中多个函数的函数名,函数类型,参数等是相同的
- 而重载则是多个函数原型(参数)不同
变量的存储类型和生存周期
- C++中的变量有数据类型和存储类型
- auto自动变量
- register寄存器变量
- extern用于声明全局变量
- static静态变量
- 不同的变量再内存中存在的时间称为变量的生存期
- 不同存储类型的变量其生存期不同:静态生存期变量/动态生存期变量
- 动态:auto,register
- 静态:全局变量,静态变量
子对象
当一个类的成员是某一个类的对象时,该对象就为子对象
自身类对象不可作为类的成员
在类中出现了子对象或对象成员(另一个类的对象)时,该类的构造函数要包含对子对象的初始化
派生类构造函数必须对这三类成员进行初始化:
- 基类成员函数
- 子对象构造函数
- 派生类构造函数
- 析构函数的调用顺序则恰好相反
对象与类
- 类是对象的抽象,对象是类的实例
- 类是一个自定义数据类型,对象是该数据类型的一个变量
继承
- 定义新类是通过继承机制实现的
- 派生类继承了基类的所有成员,但不包括构造函数,析构函数和默认赋值运算符
- 派生类的构造函数要初始化本类的数据成员,还要调用基类的构造函数,并为基类构造函数传递参数
- 派生类构造函数的成员初始化列表中,包含:基类的构造函数,派生类中子对象的初始化,派生类中一般数据成员的初始化,不能包含基类中子对象(基类的数据成员)的初始化(这个东西的初始化是通过基类的构造函数)
- 对基类数据成员的初始化必须在派生类的初始化列表中执行
- 子对象的构造函数调用的顺序是按照类的定义的顺序来的
继承方式和访问控制
单独一个类的访问控制权限
- public成员允许被类外的函数访问
- protected成员允许被本类的成员函数或者派生类的成员函数访问
- private成员只能被本类的成员函数访问
访问控制
- protected
- protected成员不能被本类对象直接访问
- protected成员不能通过派生类对象直接访问
- protected成员可以被本类成员函数和派生类成员函数访问
- public继承的public成员
- 可以派生类对象直接访问
- protected
继承方式
- 对于派生类成员函数,无论什么继承方式,其都可以访问基类的public和protected
- public继承,派生类对象只能访问基类public成员
类与继承编程
可以将字符指针直接赋值给string字符串,需要使用strcpy()才可将字符指针赋给字符数组
库中 对象数组初始化的方法:
- 类名 数组名[]={构造函数调用, , };
调用外部函数时,需要将实参值复制给形参,如果类的数据成员较多,可以使用对象指针或对象引用传递函数参数
动态创建一个对象并初始化
clock c1;//clock为类名 c1=new clock (形式参数表);
经典代码语句:
name=new char [strlen(c)+1]; strcpy(name,c);
析构与拷贝
- 拷贝构造函数的形参一定是对象的引用
- 如果外在函数本身带有引用,则不调用类中的拷贝构造函数,并且对于引用不再次析构
- 如果外在函数直接传值得话,系统会调用拷贝构造函数,但是会创建对象的副本,这个副本会调用析构函数
#include
using namespace std; class clock { public: clock() { cout << "构造" << endl; } clock(clock& c) { cout << "拷贝1" << endl; } /*clock(clock c) { cout << "拷贝2"; }*/ ~clock() { cout << "析构" << endl; } }; void newclock1(clock c1) { cout << "函数1" << endl; } void newclock2(clock& c2) { cout << "函数2" << endl; } int main() { clock c; newclock1(c); newclock2(c); } //输出结果为: 构造 拷贝1 函数1 析构 函数2 析构 出现指针的形参能加const就加const
指向类数据成员的指针
通过对象来引用指针所指向的成员
<类对象名>.*<指向非静态数据成员的指针>
<类对象指针>->*<指向非静态数据成员的指针>
student s("zhang",100); cout<
*ps<
struct的字节对齐问题
struct student { int id; char name[16]; double score; }; student s1; cout << sizeof(student); cout << &s1.id << endl; cout << &s1.name << endl; cout << &s1.score << endl; /*输出结果为: 32 003BFA4C 003BFA50 003BFA64 int占了4个字节,然后char占了16个字节,最后double寻址的时候发现16-24被占用了,所以占用了第24-32字节,所以总共占用了32个字节 */
double和int这一类的分配内存只针对它自己的字节,char比较特殊,如果结构体中只有单独的char,是补不齐的,char是多长结构体就是多少个字节,但是char和其他类型的都在结构体中就会出现常规意义上的补齐
结构体中有double,那么double附近的东西会被补成8个字节空间,最后字节长度会是8的倍数
关于short和char,char会被补到2的倍数
易错:
手写调用函数时,记得加括号
//a[i][j]实际上是第i+1行,第j+1列
联合和枚举类型定义后要加分号;
借鉴:
- 指针指向的不是一个值,而是一个变量,比如++(*p),不仅输出的值加一,p指向的变量也加一
- 引用是对变量的直接访问,指针是对变量的间接访问
- 在动态申请数组空间时,不可以对数组进行初始化