把自己印象笔记中所记录的一些C++知识点整合了一下,可用于面试前对C++知识的快速回顾。
不过并不全,只是自己笔记中的摘要,重要的还是系统和踏实地学习。
每个知识点不分顺序。
1.typeid是什么
typeid用于类的类型检查
同一类型
指针类
D d;
C *p=d;
typeid(*p)==typeid(D)则满足
返回类型是type_info
2.define的一些注意点
#define a 10
void
foo();
main(){
printf
(
"%d.."
,a);
foo();
printf
(
"%d"
,a);
}
void
foo(){
#undef a
#define a 50
//只影响这句话之后的内容
}
答案输出10..10
另外,不管是在某个函数内,还是在函数外,define都是从 定义开始直到文件结尾 ,所以如果把foo函数放到main上面的话,则结果会是50 ,50
#define a 10
void foo();
void prin();
int main() {
prin();
printf ( "%d " , a);
foo();
printf ( "%d " , a);
}
void foo() {
#undef a
#define a 50
}
void prin() {
printf ( "%d " , a); //此处的a已经被替换
}
上面代码输出 50 10 10,可以看出define只是在预处理阶段将a替换为相应数值,具体替换的值,只与define在文件中的位置有关,与是否在函数内无关。
3.重载,重定义,重写是什么?
1.重写(override):
父类与子类之间的多态性。子类重新定义父类中有相同名称和参数的虚函数。
1)被重写的函数不能是static的。必须是virtual的(即函数在最原始的基类中被声明为virtual )。
2)重写函数必须有相同的类型,名称和参数列表(即相同的函数原型)
3)重写函数的访问修饰符(public...)可以不同。尽管virtual是private的,派生类中重写改写为public,protected也是可以的
2.重载(overload):
指函数名相同,但是它的参数表列个数或顺序,类型不同。但是不能靠返回类型来判断。
3.重定义(redefining):
子类重新定义父类中有相同名称的非虚函数(参数列表可以不同)。
重写与重载的区别 (override) PK (overload)
1、方法的重写是子类和父类之间的关系,是垂直关系;方法的重载是同一个类中方法之间的关系,是水平关系。
2、重写要求参数列表相同;重载要求参数列表不同。
3、重写关系中,调用那个方法体,是根据 对象的类型(对象对应存储空间类型)来决定;
重载关系,是根据 调用时的实参表与形参表来选择 方法体的。
4.C++中的空类,默认产生哪些类成员函数?
class Empty
{
public:
Empty(); // 缺省构造函数
Empty( const Empty& ); // 拷贝构造函数
~Empty(); // 析构函数
Empty& operator=( const Empty& ); //
赋值运算符
Empty* operator&(); //
取址运算符
const Empty* operator&() const; //
取址运算符 const
};
5.运算符重载
规则:
不可重载"." 或"*",这2个是用来访问成员的
不可重载“::”,作用域分辨符的操作数是类型
不可重载“?”,是三目运算符
返回类型 operator 运算符 (形参) {
函数体
}
成员函数的运算符重载比非成员函数少一个形参
因为自身this就是一个
重载自加运算符后可以返回对象的引用, 以方便在表达式中连续使用。
举个例:
cout<<其实是重载了<<这个操作符,cout是一个ostream的对象。如果不返回引用,cout<<a<<b<<endl;
若不是引用,则cout<<a就不是一个输出流对象,就无法继续接b在屏幕上输出了。
就不可以一起写了。如果返回自身的引用 cout<<a之后,返回身身的引用,后面可以继续接b了。就是这个意思吧。好多重载操作符,作用其实都是这样的。
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
class z;
ostream & operator<< (ostream &out,z z1);
class z{
public:
int real,imag;
z(int r=0,int i=0):real(r),imag(i){};
z operator+ (const z &z1) const;
z operator- ();//无参数,前置负号
z &operator++ () ; //前置,即++z
z operator++ (int) ; //后置,即z++
friend ostream & operator<< (ostream &out,z z1);
void show();
};
//友元函数,不用加::
ostream & operator<< (ostream &out,z z1)
{
out<<"("<<z1.real<<","<<z1.imag<<")";
return out;
}
z z::operator+ (const z &z1) const
{
return z(this->real+z1.real,imag+z1.imag);
}
z &z::operator++ ()
{
real++;
imag++;
return *this;
}
z z::operator++ (int )
{
return z(real++,imag++);
}
z z::operator- ()
{
return z(-real,-imag);
}
void z::show()
{
printf("(%d,%d)\n",real,imag);
}
int main()
{
z a(1,5),b(3,1);
cout<<"a+b=";
(a+b).show();
(a++).show();
a.show();
(++b).show();
b.show();
(-b).show();
cout<<"a="<<a<<endl;
}
6.模板类和类模板的区别
模板定义:模板就是实现代码重用机制的一种工具,它可以实现类型参数化,即把类型定义为参数,从而实现了真正的代码可重用性。模版可以分为两类,一个是函数模版,另外一个是类模版。
Template <class或者也可以用typename T>
返回类型 函数名(形参表)
{//函数定义体 }
//T可以被任何字母或者数字代替。
template <class T>
T min(T x,T y)
{
return(x<y)?x:y;
}
模板类和重载函数一起使用时:
两者一起使用时, 先考虑重载函数 , 后考虑模板类 ,如过再找不到,就考虑类型转换,可能会带来精度的变化。
类模板:
template <class T>
class Base
{
public :
T a ;
Base(T b) {
a = b ;
}
return a ;} //类内定义
void setA(T c);
};
template <class T> //模板在类外的定义
void Base<T>::setA(T c)
{
a = c ;
}
7.static 和 const 的作用
static关键字至少有下列5个作用:
(1) 函数体内变量 。函数体内static变量的作用范围为该函数体,不同于auto变量,该变量的内存只被分配一次,因此其值在下次调用时仍维持上次的值 ;
(2) 模块内全局变量 。在模块内的static全局变量可以被模块内所用函数访问,但不能被模块外其它函数访问;
(3) 模块内函数 。在模块内的static函数只可被这一模块内的其它函数调用,这个函数的使用范围被限制在声明它的模块内;
(4) 类内成员变量 。在类中的static成员变量属于整个类所拥有,对类的所有对象只有一份拷贝;
(5) 类内成员函数 。在类中的static成员函数属于整个类所拥有,这个函数不接收this指针,因而只能访问类的static成员变量。
const关键字至少有下列n个作用:
(1)欲 阻止一个变量被改变 ,可以使用const关键字。在定义该const变量时,通常需要对它进行初始化,因为以后就没有机会再去改变它了;
(2)对指针来说,可以指定指针本身为const,也可以指定指针所指的数据为const,或二者同时指定为const;
(3)在一个函数声明中, const可以修饰形参 ,表明它是一个输入参数,在函数内部不能改变其值;
(4)对于类的成员函数,若指定其为const类型,则表明其是一个 常函数,不能修改类的 成员变量 ;
(5)对于类的成员函数,有时候必须 指定其返回值为const类型 ,以使得其返回值不为“左值”。例如:
const classA operator*(const classA& a1,const classA& a2);
operator*的返回结果必须是一个const对象。如果不是,这样的变态代码也不会编译出错:
classA a, b, c;
(a * b) = c; // 对a*b的结果赋值
操作(a * b) = c显然不符合编程者的初衷,也没有任何意义。
8.String类的实现
编写类String的构造函数、析构函数和赋值函数,已知类String的原型为:
class String {
public :
String( const char *str = NULL); // 普通构造函数
String( const String &other); // 拷贝构造函数
~ String( void ); // 析构函数
String & operator =( const String &other); // 赋值函数
private :
char *m_data; // 用于保存字符串
};
参考答案
//普通构造函数
String::String( const char *str) {
if (str==NULL) {
m_data = new char [1]; // 得分点:对空字符串自动申请存放结束标志'\0'的空
//加分点:对m_data加NULL 判断
*m_data = '\0' ;
}
else {
int length = strlen (str);
m_data = new char [length+1]; // 若能加 NULL 判断则更好
strcpy (m_data, str);
}
}
// String的析构函数
String::~String( void ) {
delete [] m_data; // 或delete m_data;
}
//拷贝构造函数
String::String( const String &other) // 得分点:输入参数为const型 {
int length = strlen (other.m_data);
m_data = new char [length+1]; //加分点:对m_data加NULL 判断
strcpy (m_data, other.m_data);
}
//赋值函数
String & String::operate =( const String &other) // 得分点:输入参数为const型 {
if ( this == &other) //得分点:检查自赋值
return * this ;
delete [] m_data; //得分点:释放原有的内存资源
int length = strlen ( other.m_data );
m_data = new char [length+1]; //加分点:对m_data加NULL 判断
strcpy ( m_data, other.m_data );
return * this ; //得分点:返回本对象的引用
}
9.assert的作用
参数:Expression (including pointers) that evaluates to nonzero or 0.(表达式【包括指针】是非零或零)
原理:assert的作用是现计算表达式 expression ,如果其值为假(即为0),那么它先向stderr打印一条出错信息,然后通过调用 abort 来终止程序运行。
用法总结:
1) 在函数开始处检验传入参数的合法性
如:
int resetBufferSize(int nNewSize)
{
//功能:改变缓冲区大小,
//参数:nNewSize 缓冲区新长度
//返回值:缓冲区当前长度
//说明:保持原信息内容不变 nNewSize<=0表示清除缓冲区
assert
(nNewSize >= 0);
assert
(nNewSize <= MAX_BUFFER_SIZE);
...
}
2) 每个assert只检验一个条件 , 因为同时检验多个条件时,如果断言失败,无法直观的判断是哪个条件失败
不好: assert (nOffset>=0 && nOffset+nSize<=m_nInfomationSize);
好: assert (nOffset >= 0);
assert (nOffset+nSize <= m_nInfomationSize);
3) 不能使用改变环境的语句, 因为 assert 只在DEBUG个生效,如果这么做,会使用程序在真正运行时遇到问题
错误: assert (i++ < 100)
这是因为如果出错,比如在执行之前i=100,那么这条语句就不会执行,那么i++这条命令就没有执行。
正确: assert (i < 100);
i++;
4) assert和后面的语句应空一行 ,以形成逻辑和视觉上的一致感
5)有的地方, assert 不能代替条件过滤
ASSERT 只有在Debug版本中才有效,如果编译为Release版本则被忽略掉。(在C中, ASSERT 是宏而不是函数),使用 ASSERT “断言”容易在debug时输出程序错误所在。
而 assert ()的功能类似,它是ANSI C标准中规定的函数,它与 ASSERT 的一个重要区别是可以用在Release版本中。
使用 assert 的缺点是,频繁的调用会极大的影响程序的性能,增加额外的开销。
在调试结束后,可以通过在包含#include < assert .h>的语句之前插入 #define NDEBUG 来禁用 assert 调用,示例代码如下:
void* memcpy(void *dst, const void *src, size_t count)
{
//安全检查
assert( (dst != NULL) && (src != NULL) );
unsigned char *pdst = (unsigned char *)dst;
const unsigned char *psrc = (const unsigned char *)src;
//防止内存重复
assert(!(psrc<=pdst && pdst<psrc+count));
assert(!(pdst<=psrc && psrc<pdst+count));
while(count--)
{
*pdst = *psrc;
pdst++;
psrc++;
}
return dst;
}
10.函数指针的用法
int max(int a,int b)
{
cout<<a+b<<endl;
return a+b;
}
int (*p)(int,int)=max;
int main()
{
(*p)(1,2); //都可以!
p(1,2);
}
函数指针
Int and(int a,int b){return a+b;}
int (*p)(int ,int );
p=and;
int c=(*p)(1,2);
11.枚举变量
枚举元素不是字符常量也不是字符串常量,使用时不要加单、双引号。
只能把枚举值赋予枚举变量,不能把元素的数值直接赋予枚举变量
enum e{a,b,c=5,d,e} x;
int main()
{
char *s="abcde";
x=(enum e)d; //d代表6,a代表0,b代表1
switch(x)
{
case a:cout<<"a"<<endl;break;
case b:cout<<"b"<<endl;break;
case c:cout<<"c"<<endl;break;
case d:cout<<"d"<<endl;break;
case e:cout<<"e"<<endl;break;
}
}
12.构造函数 析构函数 是不是虚函数
1. 为什么构造函数不能为虚函数?
虚函数的调用需要虚函数表指针,而该指针存放在对象的内容空间中;若构造函数声明为虚函数, 那么由于对象还未创建,还没有内存空间,更没有虚函数表地址用来调用虚函数 ——构造函数了。
2. 为什么析构函数可以为虚函数,如果不设为虚函数可能会存在什么问题?
首先析构函数可以为虚函数,而且当要使用基类指针或引用调用子类时,最好将基类的析构函数声明为虚函数,否则可以存在内存泄露的问题。
举例说明:
子类B继承自基类A; A *p = new B; delete p;
1) 此时,如果类A的析构函数不是虚函数,那么delete p; 将会仅仅调用A的析构函数,只释放了B对象中的A部分,而派生出的新的部分未释放掉。
2) 如果类A的析构函数是虚函数,delete p; 将会先调用B的析构函数,再调用A的析构函数,释放B对象的所有空间。
补充: B *p = new B; delete p;时也是先调用B的析构函数,再调用A的析构函数
13.特殊类型变量
如果一个变量被频繁使用,需保存在寄存器中,因为寄存器的速度要比内存快的许多。在早期的编译器中需要手动定义为register型,但是后来编译器可以自动将调用次数多的变量放入寄存器中。
auto: 默认的分配类型。一般不需要手动声明了,C++11特性;
auto s="abc",ss="edf" //auto变成string
auto a=1,*p=2 //相当于aoto变成int
auto a=1,c="abc" //出错,2者类型不一致
static:静态分配内存。变量在整个作用域内是全局变量。
extern : 声明为外部变量;在函数的外部定义变量;
Volatile
修饰符,修饰后的变量可以防止被编译优化,每次取值时会逐语句取值(多线程)
14.二重指针寻址
#include <stdio.h>
void f( char **p){
*p += 2 ;
}
main()
{
char *a[] = { "123" , "abc" , "456" },**p;
p = a;
f(p);
printf( "%s\r\n" ,*p);
}
输出3
*p+=2;就相当于*p=*p+2;
其中*p指向字符串“123”的第一个元素,即‘1’,指针p向后移两个元素的地址,即指向‘3’
而*(p+2)才是基于p每次向后移一个字符串的长度,即*(p+2)指向“456”
15.STL一级容器
STL中一级容器是指, 容器元素本身是基本类型, 非组合类型。(vector, deque, list.)
set, multiset中元素类型是pair<key_type, key_type>;
map, multimap中元素类型是pair<key_type, value_type>;
16.多重继承的优缺点
多继承或者继承都是为了实现类的重用和封装,优点就是减少代码量,实现算法抽象等;缺点(多继承)就是容易造成名字空间冲突(尤其对于MFC类或者其他同一个基类的一般不能使用多继承),或者所称的菱形继承。建议看看《设计模式》这本书,一般可以通过其他方法(聚合等)实现多继承的话应该避免多继承。
17.动态联编(多态)
动态联编就是程序在运行的时候知道该调用哪个函数,而不是编译阶段,所以这个机制应该是由虚函数支持的,即运行时的多态。
基类的某个成员函数声明为虚函数,派生类继承,而且同样重写该函数,那么当声明一个派生类的指针或者引用时,它所调用的函数是由该指针指向的对象确定的,这就是动态联编
多态:指当不同的对象收到相同的消息时,产生不同的动作
编译时多态:函数重载、运算符重载——静态绑定
运行时多态:虚函数机制——动态绑定
C++的多态性用一句话概括就是:在基类的函数前加上virtual关键字,在派生类中重写该函数,运行时将会根据对象的实际类型来调用相应的函数。如果对象类型是派生类,就调用派生类的函数;如果对象类型是基类,就调用基类的函数。
18.转义字符
\0 空字符(NULL) 000
\ddd 任意字符 三位八进制 (不可有大于7的数字!)
\xhh 任意字符 二位十六进制
一般转义字符,如‘\b’,由两个字符表示,其实代表一个字符,这个代表退格字符
19.重载的注意事项
重载只要求函数名相同,参数类型和个数不同,对返回值类型不做要求
不能重载‘.’,因为‘.’在类中对任何成员都有意义,已经成为标准用法。
不能重载 ?: ,因为这个运算符对于类对象来说没有实际意义,相反还会引起歧义
还有::
20.指针数组
int fun(int *p[4]),
p是包含4个指针的数组。
int a[4][4]作为形参不符合,因为他指向的内容是数组,而不是指针
int **a 二级指针,指针指向的内容还是指针,对的
int D[4][8] 对应的形参为
int(*s)[8] ( 数组指针,每个都指向对应的数组的每列)
或
int D[][8]
1、int(*p)[4];------ ptr 为指向含4个元素的一维整形数组的指针变量(是指针) 对应4个元素数组的指针
2、int *p[4];-------定义指针数组p,它由4个指向整型数据的指针元素组成(是数组)
对应指针的数组
3、int(*)[4];--------实际上可以看作是一种数据类型。也就是第一个(int(*p)[4];)
21.数组取地址的问题
a[]={1,2,3,4,5}
此时,a指代数组的首地址
而&a指代的是也是数组首地址,但是会把数组a看作一个整体
(int *)(&a+1)则指数组最后一个元素的下一个
以下代码的输出是(2 , 5)
int a[ 5 ]={ 1 , 2 , 3 , 4 , 5 };
int *ptr=( int *)(&a+ 1 );
printf( "%d,%d" ,*(a+ 1 ),*(ptr- 1 ));
int a[5]={0,1,2,3,4};
cout<<*(a++)<<endl; //此处a++是错的
作为数组名,a是一个 常量指针 ,a指向的内容可改,但是a指针的指针地址不可改。
应该让int *p=a,那么即可执行p++
当a传入函数时,退化为指针,那么就可以执行a++了
char *b="abc"
则*b="123"是错的,内容不可改,指向可改。
但b=c是对的。
22.读文件时的定位问题
#include <stdio.h>
main() {
FILE * fp;
int i,a[ 6 ]={ 1 , 2 , 3 , 4 , 5 , 6 },k;
fp = fopen( "data.dat" , "w+" );
for (i= 0 ;i< 6 ;i++) {
fseek(fp,0L, 0 ); //移动到文件开头,偏移0
fprintf(fp, "%d\n" ,a[i]); //
rewind (fp); //也是回到文件开头
fscanf(fp, "%d" ,&k);
}
fclose(fp);
printf( "%d\n" ,k);
}
的输出结果是6
则程序的输出结果是?
如果来看解析的话,估计这道题难点在于fseek和rewind两个函数不太了解。
fseek(文件,偏移量,类别)改变指针函数,,其中类别为文件开头 0,文件当前位置 1,以及文末 2。
所以,fseek(fp,0L,0)就是把文件指针fp移到里开头0字节的地方,即开始位置。
rewind相当于fseek(fp,0L,0),由此可见出题人内心腹黑,强行多考一个函数。
代码循环内流程如下:移到开始位置->写一个数字->移到开始位置->读一个数字(恰恰是刚才写的那个) 循环。
23.结构体大小判断
32位和64位系统的区别在于long和指针,32位下他们是4字节,64位下他们是8字节
short都是2字节,float都是4字节,double都是8字节,long long都是8字节
short int 2字节
long long int 8字节
遵循两条原则:一、结构体变量中成员的偏移量必须是成员大小的整数倍(0被认为是任何数的整数倍)
二、结构体大小必须是所有成员大小的整数倍。
struct A{
longa1;
shorta2;
inta3;
int*a4;
};
例如求上面这个结构体A的sizeof大小,在64位下
a1的移量为0,长度为8
a2的偏移量为0+8=8,长度为2
a3的偏移量为8+2=10,不符合int长度4的倍数,故偏移量变成12,,长度为4
a4的偏移量为12+4=16,长度为8
故总大小为16+8=24,24是所有成员大小的整数倍,故A的大小为24
23.结构体大小判断
#include <stdio.h>
main() {
FILE * fp;
int i,a[ 6 ]={ 1 , 2 , 3 , 4 , 5 , 6 },k;
fp = fopen( "data.dat" , "w+" );
for (i= 0 ;i< 6 ;i++) {
fseek(fp,0L, 0 ); //移动到文件开头,偏移0
fprintf(fp, "%d\n" ,a[i]); //
rewind (fp); //也是回到文件开头
fscanf(fp, "%d" ,&k);
}
fclose(fp);
printf( "%d\n" ,k);
}
的输出结果是6
则程序的输出结果是?
如果来看解析的话,估计这道题难点在于fseek和rewind两个函数不太了解。
fseek(文件,偏移量,类别)改变指针函数,,其中类别为文件开头 0,文件当前位置 1,以及文末 2。
所以,fseek(fp,0L,0)就是把文件指针fp移到里开头0字节的地方,即开始位置。
rewind相当于fseek(fp,0L,0),由此可见出题人内心腹黑,强行多考一个函数。
代码循环内流程如下:移到开始位置->写一个数字->移到开始位置->读一个数字(恰恰是刚才写的那个) 循环。
标签:const,函数,int,笔记,String,C++,整理,data,指针 From: https://blog.51cto.com/u_15806016/5983657