C语言部分
一级指针与二级指针,函数
指针当函数参数,以及指针的具体意义、指针的++与--问题
二级指针作为修改变量时应注意的问题,还有指针类型的匹配问题
指针在字符串中的应用
指针内存空间的开辟与销毁
数组指针与指针数组的区别
函数指针(不太考,了解即可)
基本流程控制语句,如if、while、do-while、switch、for等等
结构体、数组、二维数组遍历以及存储,以及二维数组指针表示,如何用指针代替二维数组
C++部分
namespace
const加强
const int * p 指针指向的内存空间的内容不能修改,但指针的指向可以修改
int * const p 指针指向的内容可以修改,指针的指向不能修改
const int * const * p 指针指向的内容和指针的指向都不能修改
const int & p 同上一条是一样的意思,只是写法不同
引用
引用的使用,引用作为函数参数,引用的意义(了解),const引用
int *&p 的意义
重载、重写与重定义的区别
类与对象
封装与访问控制
public
private
protected
构造函数与析构函数
构造函数与析构函数调用的时间
深拷贝与浅拷贝
拷贝构造函数(最最最重要!主要有4种拷贝构造发生的条件)
在继承中,构造函数与析构函数的调用顺序(很爱考)
构造顺序:先父类,再变量,再自己
析构顺序:先自己,再变量,后父类
对象的建立与释放(new与delete,malloc与free)
静态变量与静态成员函数的使用(static)
this指针(了解)
友元(了解,在运算符的重载中用得比较广泛)
运算符的重载(写程序题重要,读程序题不重要)
单目
双目
输入输出流
继承与派生
继承方式:private、public、protected(第三个基本用不到)
C++在继承中static的处理(常考计算)
继承中同名成员函数和处理方式
多继承带来二义性问题,以及解决办法(virtual虚继承)
多态(重要)
多态的三个条件
要有继承(父类到子类之间的继承)
要有虚函数的重写(有关键字virtual)
要有父类指针(父类引用)指向子类对象
异常处理
零碎的非重点问题
输入输出流之间的控制,比如输出几位小数,保留几个有效数字,左右对齐(可以用printf去替代)
文件的读写
常见考题
读程序题肯定一下8种
数组与二维数组遍历之间然后对数字处理进行输入输出
字符串的处理,涉及到指针或者char类型的数组,还有常见的几个标准函数比如strcpy、strcat、strstr、strchr、strcmp等等
一个类带有子类与父类的继承,然后叫你输出它的构造与析构执行
对在一些类与函数之间static不会被重置,属于全局共享,会让你输出static后面变量的值
拷贝构造函数,有4种情况会被触发
异常处理(抛出),相对考的少
引用
多态
写程序题会增加重载、友元、STL
全局变量(一个从作用域角度出发的概念):
定义在函数之外的变量。作用范围是从定义的位置开始到源文件结束。当和局部变量同名时,在局部变量起作用的范围内全局变量会被屏蔽。全局变量存在静态存储区,在程序执行过程中始终占用存储单元,不是仅在需要时才开辟单元,执行完毕释放这些空间。
用static声明的静态局部变量:
在函数调用结束后不消失,保留原值,即占用的存储单元不释放,在下一次调用时仍然保留上一次调用结束时的值。静态局部变量存储在静态存储区,在程序整个运行期间都不释放。为静态局部变量赋初值是在编译时进行的,只赋初值一次,以后每次调用函数都不再重新赋初值而只保留上一次调用函数结束时的值。若定义局部静态变量不赋初值,编译时自动赋初值为0或空字符。其他函数不能引用静态局部变量,即在其他函数中它是不可见的。
数组的初始化:
可以只给一部分元素赋值,如:a[10]={1,2,3,4,5}。其余元素默认为0
对全部元素赋值时,可以不指定数组长度,如:a[]={1,2,3,4,5},系统会自动定义a数组长度为5。
a[2][3]={{1,2,3},{4,5,6}}按行赋值或a[2][3]={1,2,3,4,5,6}会自动按排列的顺序对各元素赋初值。
a[2][3]={{1},{4}}只对第一列赋值,其他元素默认为0。
a[2][3]={{1},{2,3}},只对第一行第一列,第二行第二、三列的元素赋初值,其他默认为0。
a[2][3]={{1},{}},也可以省略某行。
a[][3]={1,2,3,4,5,6},可以省略行数,不可省略列数。
一维数组作为形参时,用array[]这样的形式表示array是一维数组名,以接受实参传来的地址,[]并无实际作用,元素个数也可以不写。但二维数组作为形参时,至少要指定列数。如:int a[2][3]和int a[][3]是合法的。
字符串变量的定义:
string s1="hello"; //合法
char str[10];str="hello"; //不合法
字符串常量以'\0'作为结束符,但存放到字符串变量中就不含'\0'了
类的零碎知识:
类的私有成员只能被本类中的成员函数引用,类外不能调用(除了友元),公有成员既可以被本类成员函数调用,也可以被类外函数调用。受保护的成员不能被类外访问,但可以被派生类的成员函数访问。
类和结构体的区别在于,没有显示声明时,类的成员默认为私有,而结构体成员默认为公有。
私有的成员函数只能被本类中的其他成员函数调用,不能被类外调用。成员函数可以访问本类中的任何成员(包括私有成员),可以引用在本作用域的有效数据。需要被外调用的成员函数指定为public,它们是类的对外接口。
类的成员函数通常在类内声明,类外定义,且声明必须在定义之前,如果函数很短也可以在类内定义。在类外定义函数时要加上作用域限定符::,如果不加,则表示函数不属于任何类,是非成员函数的一般普通函数。
对象成员的引用方法有三:通过对象名和成员运算符访问;通过指向对象的指针访问;通过对象的引用变量访问。
display()是方法,stu.display();是消息,用消息激活方法。
若调用类的方法时,没有给数据成员赋初值,则它们的值是不可预知的。
给类设置默认参数时,以声明语句中的默认参数为准,就算定义中再次设计默认参数也不起作用。如void set_time(Time&,int hour=0,int minute=0,ine sec=0);/*这是声明语句*/另外,当人为给类的对象赋初值时,默认参数被覆盖,但被覆盖的仅仅是有被赋值的数据成员,没被赋值的数据成员的值仍然是默认参数。
由于类不是一个实体,而是一种抽象类型,并不占存储空间,所以不能类定义中对数据进行初始化,这样无处容纳数据。如果类中的数据成员都是公有的,可以在定义对象时对数据成员进行初始化。但如果数据成员是私有或者受保护的,就不能用这种方法,只能通过成员函数间接地访问类的数据成员,并对其赋值。
C++用构造函数来处理对象的初始化,构造函数是一种特殊的成员函数,不需要用户调用它,而是在建立对象时自动执行。构造函数名字必须与类名同名,不能由用户任意命名,以便编译系统识别它为构造函数。它不具有任何类型和返回值。构造函数的功能由用户定义,用户根据初始化要求设计函数体的函数参数。
class Time
{
public:
Time() //定义构造成员函数,函数名与类名同名
{
hour=0; //利用构造函数对对象中的数据成员赋初值
minute=0;
sec=0;
}
void set_time(); //成员函数声明
void show_time();
private:
int hour; //私有数据成员
int minute;
int sec;
};
void Time::set_time()
{
cin>>hour;
cin>>minute;
cin>>sec;
}
void Time::show_time()
{
cout<<hour<<":"<<minute<<":"<<sec<<endl;
}
int main()
{
Time t1; //建立对象t1,同时调用构造函数t1.Time()
t1.set_time(); //对t1的数据成员赋值
t1.show_time();
Time t2; //建立对象t2,同时调用构造函数t2.Time()
t2.show_time(); //显示t2的数据成员
return 0;
}
对象t1由于执行赋值函数set_time(),因此调用构造函数时拥有的初值被覆盖,而t2中仍然是调用构造函数时得到的初值。
构造函数在类外定义一样要加作用域限定符。Time::Time(){hour=0;minute=0;sec=0;}
类对象进入其作用域时调用构造函数。
构造函数没有返回值,因为也不需要声明类型,它只是对对象进行初始化。
构造函数不需要也不能被用户调用,它由系统自动调用,并且只调用一次。
若用户没有自己定义构造函数,则系统会自动生成一个构造函数,只是这个构造函数的函数体是空的,没有参数,不执行初始化操作。
若要实现对不同对象赋不同的初始值,就要采用带参数的构造函数,在调用不同对象的构造函数时,从外面将不同的数据传递给构造函数,以实现不同的初始化。实参是在定义对象时给出的。
class Box
{
public:
Box(int,int,int); //声明带参数的构造函数
int volume();
private:
int height;
int width;
int length;
};
Box::Box(int h,int w,int l) //在类外定义带参数的构造函数
{
height=h;
width=w;
length=l;
}
int Box::volume()
{
return height*width*length;
}
int main()
{
Box box1(12,25,30); //建立对象box1,并制定box1长宽高
cout<<box1.volume();
Box box2(15,30,21); //建立对象box2,并制定box1长宽高
cout<<box2.volume();
return 0;
}
带参数的构造函数中的形参,其对应的实参在定义对象时给定。用这种方法可以对不同对象进行不同的初始化。
类外定义带参数的构造函数还可以写成:Box::Box(int h,int w,int l):height(h),width(w),length(l){}
构造函数具有相同名字,不同参数个数或不同参数类型,成为构造函数的重载。系统会自动根据函数调用的形式去确定对应的函数。没给出参数的对象,就调用无参构造函数与之对应,给出几个参数的对象,就调用有几个形参的构造函数。定义无参对象时,不要加括号。如:Box box1 而不是Box box1()
还可以给构造函数设默认值,记住是在声明时指定。如Box(int h=10;int w=10;int l=10);而在定义时不用指定默认参数。在定义对象没给实参时,系统就调用默认构造函数,各形参的值都取默认值。一个全部参数都指定默认值的构造函数也算默认构造函数,因为它可以没有实参。在这种情况下,就不能有无参构造函数同时存在了,因为一个类只能有一个默认构造函数。
一个类可以有很多构造函数,但只能有一个析构函数。
先构造的后析构,后构造的先析构,相当于栈的顺序。
在全局范围中定义的对象(即在所有函数之外定义的对象)它的构造函数在所有函数执行之前调用。main函数执行完毕或调用exit函数时,调用析构函数。若定义的是局部自动对象,则在建立对象时调用其构造函数,若函数被多次调用,则每次建立对象都要调用其构造函数。函数调用结束,释放对象时先调用析构函数。若在函数中定义静态局部对象,则只在第一次调用此函数时建立对象时调用构造函数一次,在调用结束后并不释放,因此也不调用析构函数,只在main函数结束或者调用exit函数时,才调用析构函数。
this指针指向本类对象,它的值是当前被调用的成员函数所在的对象的起始地址。
int Box::volume()
{
return (height*width*length);
}
//C++中处理为
int Box::(Box *this)
{
return (this->height*this->width*this->length);
}
//则其实是以这种方式调用的
a.volume(&a);
常对象必须有初值,且只能通过构造函数的参数初始化表对其进行初始化,且后续操作中不能被修改,因为常对象的数据成员也都是常数据成员,不能在构造函数中对其进行赋值,只能在参数初始化表中进行初始化。定义方式为const 类名 对象名[(实参列表)]或者类名 const 对象名[(实参列表)]
若对象为常对象,则不能调用该对象的非const型的成员函数,这是为了防止这些函数对常对象中的数据成员做修改。如果要引用常对象的数据成员,只需将该成员函数声明为const型即可。如,void get_time() const;常成员函数可以访问常对象的数据成员,但依然不允许修改常对象中的数据成员的值。
数据成员 | 非const成员函数 | const成员函数 |
非const的数据成员 | 可以引用,也可以改变值 | 可以引用,但不可以改变值 |
const数据成员 | 可以引用,但不可以改变值 | 可以引用,但不可以改变值 |
const对象的数据成员 | 不允许 | 可以引用,但不可以改变值 |
如果对象中所有数据成员都不可以被修改,就将对象声明为常对象,然后用const成员函数引用数据成员,起到双保险作用,保证数据成员不被修改。常对象只保证数据成员都是常数据成员,不保证成员函数都是常函数。
指向对象的常指针,Time * const ptr;其指向不能被修改,指向的单元内容可以被修改。指向常对象的指针变量,const Time * ptr;其指向可以被修改,指向的单元内容不可被修改。
一个变量若已被声明为常变量,只能用常变量指针指向它,不能用一般的指针变量指向它。
若用一个常变量指针指向一个没有被声明为const的变量时,是可以访问的,但不能修改它的值,因为在被常变量指针指向期间,该变量具有常变量的性质,不能被修改,其余不被常变量指针指向的时候仍是普通变量,可以被修改。
用常指针和常引用作函数参数,可以保证数据不能被随意修改,调用函数时又不用建立实参的拷贝,提高程序运行效率。
形式 | 含义 |
Time const t1; | t1是常对象,其值在任何情况下都不能改变 |
void Time::func()const | fun是Time类中的常成员函数,可以引用,但不能修改本类中的数据成员 |
Time * const p; | p是指向Time对象的常指针,p的值(即p的指向)不能改变 |
const Time * p | p是指向Time类常对象的指针,其指向的类对象的值不能通过指针来改变 |
Time &t1=t; | t1是Time类对象的引用,二者指向同一段内存空间 |
new运算符动态分配内存,返回一个指向新对象的指针的值,通常用一个指向本类对象的指针变量来接收该地址。
复制构造函数也是构造函数,但它只有一个参数,这个参数是本类的对象。Box::Box(const Box& b){height=b.height;width=b.width;length=b.length;}调用时:Box box2(box1);如果用户自己未定义复制构造函数,则编译系统会自动提供一个默认的复制构造函数。也可以用Box box2=box1;的方式复制对象。对象的复制和赋值不同,前者是从无到有建立一个新对象,并使它与已有对象完全相同,后者是对已有的对象进行赋值,因此必须先定义被赋值的对象,才能进行赋值。
复制构造函数被调用的情况:①建立一个新对象,并用已有的同类对象对它进行初始化。②当函数的参数为类的对象时,为了将实参对象完整传给形参,也需要建立一个实参的拷贝。③函数的返回值是类的对象的时候。
静态变量用来实现对象之间数据共享。所有对象都可以引用它。如果改变它的值,那在所有对象中的值也都改变了。静态数据不随对象的撤销而释放所占的空间,因为它是所有对象共享的,只有在程序结束时才释放。
静态变量只能在类体外进行初始化。如:int Box::height=10;(不必加static),若未对静态数据成员赋初值,则默认为0。
静态数据成员可用对象名引用,也可用类名引用。
静态成员函数没有this指针。它可以直接引用本类中的静态数据成员。
如果在本类以外的其他地方定义一个函数,在类体中用friend对其进行声明,此函数就是本类的友元函数。友元函数可以访问这个类中的私有成员。
class Time
{
friend void display(Time &);
}
void display(Time& t)
{
cout<<t.hour<<":"<<t.minute<<":"<<t.sec<<endl;
}
//因为display函数不是Time类的成员函数,不能默认一弄Time类的数据成员,必须指定要访问的对象
friend不仅可以是一般函数(非成员函数),还可以是另一个类中的成员函数。
class Date; //对Date类的提前引用声明
class Time
{
void display(Date &); //display是成员函数,形参是Date类对象的引用
}
class Date //声明Date类
{
friend void Time::display(Date &); //声明Time类中的display为友元成员函数
}
如果B是A的友元类,那么B中所有函数都是A类的友元函数,可以访问A类中的所有成员。
友元的关系是单向的,且不可传递。
运算符被重载后,不会使原有功能消失或改变,只是对功能进行扩充,至于到底执行哪种功能,由表达式上下文决定,即运算符两侧的数据类型决定。
不能重载的运算符有:.(成员访问运算符)/.*(成员指针访问运算符)/::(域运算符)/sizeof(长度运算符)/?:(条件运算符)
重载不能改变操作数个数,不能改变运算符优先级别,不能改变运算符的结合性,不能有默认的参数,必须和用户定义的自定义类型对象一起使用,其参数至少有一个是类对象(或类对象的引用)。
运算符重载函数可以是类的成员函数,也可以是类的友元函数。区别在于若作为类的成员函数,可以通过this指针自由地访问本类的数据成员,因此可以少写一个函数的参数,但必须要求运算表达式的第一个参数是一个类对象,而且与运算符函数的类型相同。将双目运算符重载为友元函数时,在函数的形参表中必须有两个参数,不能省略,参数顺序任意,也不要求第一个参数必须为类对象,但在使用时必须一一对应。
/*运算符重载函数作为成员函数*/
class Complex
{
Complex operator + (Complex &c2);
};
Complex Complex::operator + (Complex &c2)
{
Complex c;
c.real=real+c2.real; //real==this->real
c.imag=imag+c2.imag;
return c;
}
/*运算符重载函数作为友元函数*/
class Complex
{
friend Complex operator + (Complex &c1,Complex &c2);
};
Complex operator + (Complex &c1,Complex &c2)
{
return Complex(c1.real+c2.real,c1.imag+c2.imag);
}
类外不能只能引用基类的私有成员,只能通过基类的公有成员函数来引用基类的私有成员。
公有基类中的成员 | 私有成员 | 公用成员 | 保护成员 |
在公用派生类中的访问属性 | 不可访问 | 公用 | 保护 |
私有基类中的成员 | 私有成员 | 公用成员 | 保护成员 |
在私有派生类中的访问属性 | 不可访问 | 私有 | 私有 |
保护基类中的成员 | 私有成员 | 公用成员 | 保护成员 |
在保护派生类中的访问属性 | 私有 | 保护 | 保护 |
既然声明为私有继承,就表示将原来能被外界引用的成员隐藏起来,不让外界引用,因此私有基类的公用成员和保护成员理所当然地成为派生类的私有成员。私有基类的私有成员按规定只能被基类的成员函数引用,在基类外(即派生类)当然不能访问他们。
保护基类的所有成员在派生类中都被保护起来,类外不能访问,其公有成员和保护成员可以被其派生类的成员函数访问(子女可以打开)
派生类中成员的4种不同访问属性:①公用的,派生类内和派生类外都可以访问。②受保护的,派生类内可以访问,派生类外不能访问,下一层的派生类可以访问③私有的,派生类内可以访问,派生类外不能访问④不可访问的,派生类内和类外都不可访问。
无论哪种访问属性,派生类都是不能访问基类的私有成员的。私有成员只能被本类的成员函数访问。
基类的构造函数不能继承,因此对继承过来的基类成员初始化工作也要由派生类的构造函数承担。具体做法是执行派生类构造函数时调用基类的构造函数。在函数体中只对派生类新增的数据成员初始化。
派生类的构造函数任务包括:①对基类数据成员初始化②对子对象数据成员初始化③对派生类数据成员初始化
执行派生类构造函数的顺序:①调用基类构造函数,对基类数据成员初始化②调用子对象构造函数,对子对象数据成员初始化③执行派生类构造函数本身,对派生类数据成员初始化。
多层派生的构造函数只要调用上一层派生类(直接基类)的构造函数即可。
如果在基类中没有定义构造函数或者定义了没有参数的构造函数,那么在定义派生类构造函数时可以不写基类构造函数。在调用派生类构造函数时,系统会自动首先调用基类的默认构造函数。
如果在基类或子对象类型的声明中定义了带参数的构造函数,就必须显式地定义派生类的构造函数。
若基类中存在同名数据成员,引用时为了避免二义性,可以用域限定符指明是哪个基类的数据成员,如:Teacher::name
如果基类和派生类有同名成员,则访问的是派生类的成员,基类的成员被屏蔽。在这种情况下要访问基类成员,需要用域限定符指明。
当声明派生类时将virtual加到相应的继承方式前,这样声明过后,当基类通过多条派生路径被一个派生类继承时,该派生类只继承该基类一次,即基类成员只保留一次。
class A //定义基类A
{
A(int i) {} //基类构造函数,有一个参数
};
class B virtual public A //A作为B的虚基类
{
B(int n):A(n){} //B类构造函数,在初始化表中对虚基类初始化
};
class C virtual public A //A作为C的虚基类
{
C(int n):A(n){} //C类构造函数,在初始化表中对虚基类初始化
}
class D :public B,public C //类D的构造函数,在初始化表中对所有基类初始化
{
D(int n):A(n),B(n),C(n){}
};
规定在最后的派生类中不仅要负责对其直接基类进行初始化,还要负责对虚基类初始化。
class Graduate:public Teacher,public Student //Teacher和Student为直接基类
{
public:
Graduate(string nam,char s,int a,string t,float sco,float w):
Person(nam,s,a),Teacher(nam,s,a,t),Student(nam,s,a,sco),wage(w){}
private:
float wage;
}
//要用最后一个派生类对所有直接基类和虚基类进行初始化
通过指向基类对象指针,只能访问派生类中的基类成员,而不能访问派生类增加的成员。
派生类对象可以对基类对象赋值。
派生类对象可以替代基类对象向基类对象的引用进行赋值或初始化。
如果函数的参数是基类对象或基类对象的引用,相应的实参可以用子类对象。
派生类对象的地址可以赋给指向基类对象的指针变量,即指向基类对象的指针变量也可以指向派生类对象。
静态多态性是通过函数的重载实现的。动态多态性是在程序运行过程中才动态确定操作所针对的对象。又称运行时多态。动态多态性是通过虚函数实现的。
在基类用virtual声明成员函数为虚函数。这样就可以在派生类中重新定义此函数,为它赋予新的功能,并能方便的被调用。
在派生类中重新定义此函数,要求函数名、函数类型、函数参数个数和类型全部与基类的虚函数相同,并根据派生类的需要重新定义函数体。
定义一个指向基类对象的指针变量,并使它指向同一类族中的需要调用该函数的对象。
通过该指针变量调用此虚函数,此时调用的就是指针变量指向的对象和同名函数。
纯虚函数是基类不需要用到但派生类可能用到时,在基类预留的函数名,其具体功能留给派生类根据需要去定义。声明纯虚函数的一般形式是:virtual 函数类型 函数名(参数列表)=0;
如果在一个类中声明纯虚函数,但在派生类中没有对该函数定义,则该函数在派生类中仍是纯虚函数。
若用new运算符建立临时对象,且基类中有析构函数,并定义一个指向该基类的指针变量,在程序用delete运算符撤销对象时,只会执行基类的析构函数而不执行派生类的虚构函数。如果希望能执行派生类的析构函数,可将基类的析构函数声明为虚析构函数。最好把基类的析构函数声明为虚函数,由该基类派生的派生类的析构函数都会自动成为虚函数,及时派生类的析构函数与基类析构函数名字不相同。
有一些类被定义的唯一目的是用它作为基类去建立派生类,它作为一种基本类型提供给用户,用户在这个基础上根据自己的需要定义出功能各异的派生类。用这些派生类去建立对象。这就叫抽象类。凡是包含纯虚函数的类都是抽象类。纯虚函数不能被调用,因此抽象类不能建立对象,抽象类的作用是作为一个类族的共同基类,为一个类族提供一个公共接口。
虽然抽象类不能定义对象,但可以定义指向抽象类数据的指针变量,当派生类为具体类之后,就可以用这种指针指向派生类对象,通过该指针调用虚函数,实现多态性操作。
throw抛出异常信息后,流程立即离开本函数,转到其上一级的函数。因此不会执行当前函数后面的语句。
catch必须紧跟在try后面,不能单独使用,两者之间也不能插入其他语句。
throw的异常是什么类型的就去找与之类型匹配的catch块。
在一个try-catch结构中,可以只有try块而无catch块。即在本函数中只检查不处理,把catch处理放在其他函数中。
try和catch必须用花括号括起来,不能省略。
一个try-catch结构只能有一个try块,但可以有多个catch块与不同的异常信息匹配。