C++基础篇之面试笔记-1

{//=====面向对象 封装 继承 多态 -> 对人不对事

{//面向对象(对人) VS 面向过程(对事)
C语言采用面向过程的方式,以过程为中心,将分析解决问题的步骤和流程以函数的方式实现。
C++采用面向对象的方式,以实物为中心,一切实物皆是对象,通过面向对象的方式,将现实世界抽象成对象。

{//例: 五子棋
面向过程(按步骤走) //1开始游戏 ->2黑子先走-> 3绘制画面 -> 4判断输赢 -> 5轮到白子 -> 6 绘制画面 -> 7判断输赢,8返回步骤2,9 输出最后结果
面向对象 //找出对象: 棋手 棋盘 裁判
//对象间交互演化万千变化: 黑方走哪个位置,白方如何应对 -> 变中求不变( 事是人做的,事可变化万千,但人不变)
}

注 //c是面向过程,c++是面向对象说的时语言特性, 不是编程思想
//编程思想不受语言控制,汇编也能面向对象,只不过写起来别扭。
}

{//谈谈对面向对象理解
面向对象:
通俗的讲一个项目中有多个对象组成(对象加对象的方式),如果一个对象出现了问题另一个对象还能进行工作
一切事物皆是对象,通过面向对象的方式,将现实世界和事物抽象成对象。
面向对象大致分为封装、继承和多态几部分。
}

{//引用与指针有什么区别?

  1. 引用必须被初始化,指针不必。
  2. 引用初始化以后不能被改变,指针可以改变所指的对象。
  3. 不存在指向空值的引用,但是存在指向空值的指针。
引用的作用范围:
	通常是作为函数传参使用,可以尽量减少使用指针,还可以提升效率。

引用的作用:对变量取别名
	引用变量必须初始化,表名是给谁取别名
	int a = 5;
	int &b = a;//给a变量取别名b,b的类型:int &

}

{//c和c++中的struct有什么不同?
C++中的结构体可以直接使用结构体名作为结构体的类型,C中不可以。
C++中的结构体里面可以含有成员函数,而C中不可以
//c++中struct和class的主要区别
在于默认的存取权限不同,struct默认为public(公有的),而class默认为private(私有的)
}

{//静态变量和动态变量
1.联编(链接)
就是将模块或者函数合并在一起生成可执行代码的处理过程。 按照联编所进行的阶段不同, 可分为两种不同的联编方法: 静态联编和动态联编。
2.静态联编(静态链接)
是指在编译阶段就将函数实现和函数调用关联起来, 因此静态联编也叫早绑定。
3.动态联编(动态链接)
是指在程序执行的时候才将函数实现和函数调用关联, 因此也叫运行时绑定或者晚绑定。 C++中一般情况下联编也是静态联编, 但是
一旦涉及到多态和虚拟函数就必须要使用动态联编了。
}

{//-----------------------对象(类)---------------------- 独立个体的描述,包含静态属性和动态行为(方法)
//类:共有的东西抽取出来
//对象:具体的某一个东西
}

{//构造函数和析构函数

	{//构造函数
		作用:初始化成员变量
		定义:构造函数与类同名的函数
		注意:
			构造函数不是由用户手动调用的,而是定义对象时主动调用
			构造函数可以传参,没有返回值
			如果没有构造函数,类中会自定添加一个无参的构造函数
			如果自定义构造函数,则类中不会再添加默认的构造函数
			当定义对象时,会调用对应参数的构造函数		
	//为什么C++ 中要定义构造函数和析构函数?
		C语言编程的时候,通常生成的变量都是放在栈区里,然而真正处理实际问题的程序将变量或数组放在堆区。
		当定义一个对象,此对象有一个占存储很大的成员,则我们不希望出现在栈区,而是希望它出现在堆区。
		为了解决这种办法,C++提供了一种成员函数,在初始化对象的时候就自动调用它,称为构造函数。
	}
	
	{//析构函数
			定义:在一个对象的生命周期即将结束的时候,应该回收对象所占有的资源。
			函数形式:
				~类名();		----->析构函数没有返回值,也没有参数,所以不能重载
			注意:
				析构函数实在对象被销毁时自动被调用,释放对象所占资源
				析构函数还可以手动调用(用途:对程序长期运行的静态成员手动释放空间)
				当显示调用(手动调用)析构函数时,相当于调用普通的成员函数。
	}
	{//创建一个类,会自动生成什么函数?
			构造函数、析构函数、复制构造函数、赋值运算符、地址运算符
	}
	{//new和malloc区别
			Demo *p = new Demo;//调用构造函数
			delete p;//调用析构函数
			
			Demo *q = (Demo *)malloc(sizeof(Demo));//不会调用构造函数
			free(q);//不会调用析构函数
	}

}

{//复制构造函数(深拷贝和浅拷贝)

//三种情况可使用复制构造函数
	当一个对象用于给另外一个对象进行初始化时
	一个对象作为函数参数,以值传递的方式进入函数体
	一个对象作为函数的返回值,以值传递的方式从函数返回


复制构造函数的命名方式;	
	Integer::Integer(const Integer &obj);//构造函数的参数必须是引用
	
复制构造函数调用:
	Integer obj1 = obj;	//复制构造函数
	Integer obj1(obj);	//复制构造函数
	obj1 = obj;			//赋值
注意:
	当类中没有手动添加复制构造函数,则会在类中自动生成一个复制构造函数,
	该自动生成的复制构造函数会把obj中的成员变量依次赋给obj1

{//深拷贝和浅拷贝
		浅拷贝:
			构造函数生成一片空间,调用复制构造函数也会自动生成一片空间。
			obj1 = obj;所以构造函数和复制构造函数同时指向一片空间,
			所以arr一样,所以会造成double free
		深拷贝:
			所以要在复制构造函数obj1中在开辟一段空间
}

}

{//----------------------封装(打包)--------------------- 信息隐藏(模块化) -> 接口和实现相分离(信息隐藏起来,只关心接口)
对象的封装
把抽象出来的东西包装起来组成一个类型-----> 使得代码模块化(class)
是接口和实现相分离
类比:箱子打包 -> 隐藏细节,分类划分清
}

{//-------------------------继承------------------------ 派生类可以继承基类的功能,并进行扩展(在已有的基础上新建一个类) -> 重用 扩展

{//类的继承:
派生类可以继承基类的功能,并进行扩展(是 is a关系) -> 重用(可重用基类的方法), 扩展,理清对象间关系(继承派生关系)
在已有的基础上新建一个类,就叫继承
为什么用继承
比如在QT界面中设计一个类似按钮的功能,用继承可以在按钮的基础上对新创建的按钮进行设计,提高效率

2.已存在的类:称为“基类(base class)”或“父类(father class)”。
3.新建立的类称为“派生类(derivedclass)”或“子类(son class)”。

{//类比 
生物 -> 植物 -> 树木   -> 银杏树 楸树
                花草   -> 玫瑰  忘忧草		
        动物 -> 飞行动物 -> 鸟 -> 大雁                                  
                                  鸭子  -> 唐老鸭                
             -> 爬行动物 -> 猫 狗

人  ->  男人 -> 父亲 -> 儿子 
        老师  -> 大学老师
        学生  -> 留学生                                          

角色 -> 法师
        战士
        骑兵                                
} 
 
{//派生类继承基类
	当定义派生类对象时:
		1.基类的构造函数
		2.派生类的构造函数
		.......
		3.派生类的析构函数
		4.基类的析构函数(先构造的后释放)
		class Base{	
			public:
				Base(){cout << "Base()" << endl;}
				~Base(){cout << "~Base()" <

}

}

{//多重继承 多重继承就是一个派生类有多个直接继承的基类(子类可以继承两个以上的父类)
{//注:多重继承容易产生歧义(多个基类中共享用的成员被继承)
多重继承就是一个派生类有多个直接继承的基类,是单一继承的一种扩展,派生类与每个基类之间的关系仍可看作是一个单继承。
class Wolf{
public:
Wolf(){cout << “Wolf()” << endl;}
~Wolf(){cout << “~Wolf()” < void sleep(){cout << “Wolf: zzzzzzzzzzzzzzzzz!\n”;}
private:
};
class Man{
public:
Man(){cout << “Man()” << endl;}
~Man(){cout << “~Man()” < void sleep(){cout << “Man: zzzzzzzzzzzzzzzzz!\n”;}
};
class wolfMan: public Wolf, public Man{
public:
wolfMan(){cout << “wolfMan()” << endl;}
~wolfMan(){cout << “~wolfMan()” < };
int main(int argc, char *argv[])
{
wolfMan obj;
// obj.sleep();//歧义,因为Wolf和Man中都有sleep()

		//解决歧义的办法
		obj.Wolf::sleep();
		obj.Man::sleep();
	}
	/******************************************************************
		打印结果:
			Wolf()
			Man()
			wolfMan()
			Wolf: zzzzzzzzzzzzzzzzz!
			Man: zzzzzzzzzzzzzzzzz!
			~wolfMan()
			~Man()
			~Wolf()
	********************************************************************/
}

注: //多重继承在项目中,尽量少用,它把对象间关系变复杂了,
  //不好维护扩展,常是很多项目bug的根源,	
  //象java中就不支持多重继承(除了接口支持外)

}

{//----------------------多态(未来性)------------------- 适应未来变化 -> 接口重用(父类自适应调子类方法)
多态定义:
一个接口,多种方法
多态性是将接口和实现进行分离,简单来说就是实现共同的方法,但因个体差异,而采用不同策略。
虚函数:派生类和基类有共同的方法,但是行为不同
虚函数就是为了实现多态性

多态(多种形态) 对象能适应环境变化,改变状态
分类: 静态多态 - 重载 + 模板(泛型)
动态多态 - 虚函数
虚函数:即要做一件事情(父类函数),不确定未来具体会怎么做时(子类函数)
让父类调用函数时,能动态根据不同子类调用对应的具体函数. 这种函数叫虚函数。
//优点: 接口重用,父类自适应调子类方法,方便
//缺点: 降低运行效率(多态需要去找虚表的地址),空间有浪费

类比 动物会呼吸,会根据是狗还是鱼使用不同的呼吸方式 -> 适者生存

Why 面试必考,深刻理解面向对象编程的关键。
在实际开发中,如何进行软件抽象,解耦是关键。而多态是抽象能力的重要一环。
}

{//面试:虚函数 纯虚函数 抽象类 接口 虚继承 的区别
虚函数 //有自己的实现, 用virtual来标识,是表示未来可能会被改写(多态)
纯虚函数 //用virtual来标识(只能声明,没有定义)
抽象类(虚类) //带有纯虚函数的类为抽象类
//定义了纯虚函数的类是抽象类,不能实例化
//抽象类包括抽象方法(纯虚方法),也可以包含普通方法
//抽象类可以派生自一个抽象类,可以覆盖基类的抽象方法也可以不覆盖
纯粹抽象类(接口) //完全由纯虚函数构成的类
虚继承 //虚继承就是在继承加上virtual

注 //把虚类(virtual), 划分成 抽象类(abstract 主干) 和 接口(interface 枝叶),
//这是JAVA 在建模时,对对象更深刻的思考, 抽象类代表本质主干的东西,接口代表附加的枝叶。
//接口可多重继承,抽象类不可。

}

{//面试:子类析构时要调用父类的析构函数吗?
构造次序 //先基类的构造、再派生类的构造

析构次序 //先派生类的析构后基类的析构
也就是说在基类的的析构调用的时候, 派生类的信息已经全部销毁了。

}

{//面试: C++有哪些性质(面向对象特点)
封装,继承和多态
}

}

{//为什么建议析构函数时用虚析构函数?
如:Base *p = new Internal;
delete p;
比如指针指向的对象是基类类型,所以delete销毁对象时候,并不会调用派生类的析构函数,
这样会造成对象销毁不完整
}

{//函数的覆盖、隐藏、重载

{//覆盖(也称重写)  动态绑定
	是指派生类重新定义基类的虚函数,特征如下:
		1. 在不同的作用域下(分别位于派生类与基类)
		2. 函数名相同
		3. 参数相同
		4. 返回值相同
		5. 重写函数的权限限定符 可以不同
		6. 基类函数必须有virtual关键字,不能有static
}

{//重载    静态绑定;编译期绑定。
	是指函数名相同,参数不同,特征如下:
		1. 必须在同一作用域下
		2. 函数名相同
		3. 参数不同(参数取决于类型和个数)
		4. 与返回值无关
		5. virtual 关键字有无可无
	为什么会用重载:
		比如你要进行加法运算,有小数 + 小数的加法,还有整形 + 整形的加法
		还有小数 + 整数的加法,就可以用重载,函数名相同,判断参数就可以
 原理:
 	 编译器通过把您所用的参数类型与定义中的参数类型进行比较,决定选用最合适的定义。 ->静态绑定(编译期绑定)

	为什么C语言没有函数的重载?
		支持重载问题就出在链接这个阶段上,
		c语言在链接的时候根据函数名找要调用的函数,
		而c++而是根据函数名和参数类型来寻找要调用的函数(函数名修饰规则);

}

{//隐藏
	派生类的函数屏蔽了与其同名的基类函数,注意只要同名函数,不管参数列表是否相同,基类函数都会被隐藏	
		1. 在不同的作用域下(分别位于派生类与基类)
		2. 函数名相同
		3. 返回值可以不同
		4. 参数不同。此时,不论有无 virtual 关键字,基类的函数将被隐藏
		5. 参数相同,但是基类函数没有 virtual 关键字。此时,基类的函数被隐藏
}

}

{//三种继承方式:公有继承、保护继承、私有继承
三种继承方式中哪些基类的成员可以被继承?
public:
在公有 public 继承中
1. 基类中的public成员可以被继承
2. 基类中的protected可以被继承
3. 基类中的private不可以被继承

			4. 基类中的protected成员被继承后任然是protected的
			5. 基类中的public成员被继承后任然是public的
			
			6. 基类private成员可以通过基类中的非private成员访问
	protected:
		在受保护 protected 的继承中
			1. 基类中的public成员可以被继承
			2. 基类中的protected可以被继承
			3. 基类中的private不可以被继承
			
			4. 基类的public和protected被继承后都是protected
			
			5. 基类private成员可以通过基类中的非private成员访问
	private:	
		在私有 private 的继承中
			1. 基类中的public成员可以被继承
			2. 基类中的protected可以被继承
			3. 基类中的private不可以被继承
			
			4.基类的public和protected被继承后都是private
			
			5.基类private成员可以通过基类中的非private成员访问
注:
	在所有的继承中:
		基类 private 成员都不能被继承
		基类 public/protected 成员都可以被继承
		如果是公有继承,继承后权限不变
		如果是受保护继承,继承后都是受保护的
		如果是私有继承,继承后都是私有的
		基类private成员可以通过基类中的非private成员访问

}

{//友元
//友元破坏了类的隐藏和封装,所以必须慎用
//三类友元:
友元函数
友元类
友元成员函数

{//友元函数
	可以把某一个函数设置为某个类的友元,这个函数就叫做友元函数。 friend
	
	//为什么要有友元函数?
		在实现类之间数据共享时,减少系统开销,提高效率。
		比如当要访问私有变量时,不能直接访问,但是可以定义一个友元函数实现去访问私有变量。
		
	class Integer{
			public:
				Integer(int num = 0){
					val = num;
				}
				void steVal(int num = 10){
					val = num;
				}
				int getVal(){
					return val;
				}
				friend void test();//友元函数,限制访问符,对友元没有任何影响
			private:
				int val; 
				//friend void test();//也可以写在这里,test不属于类的一部分,所以放在共有和私有的无所谓
	};
	void test()
	{
			Integer obj;
			cout << obj.val << endl;
			obj.val = 100;
			cout << obj.val << endl;
	}
	int main()
	{
			test();
	}
}
	
{//友元类
	把某一个类设置为另一个类的友元,这个类就叫做友元类
		
	友元关系不能被继承。
	友元关系是单向的, 不具有交换性。 若类B是类A的友元, 类A不一定是类B的友元, 要看在类中是否有相应的声明。
	友元关系不具有传递性。 若类B是类A的友元, 类C是B的友元, 类C不一定是类A的友元, 同样要看类中是否有相应的申明
}

{//友元成员函数
	把一个类中的某一个成员函数设置为某个类的友元,这个类的成员函数就叫做友元成员函数
	友元成员函数使用需要注意,必须在声明之后才能使用。
}

{//友元运算符重载
	返回值如果是对象,什么时候该用引用,什么时候不用引用?
			当返回的是引用参数本身时,采用引用,否则不使用引用(因为其他参数都是局部对象,会被销毁)
	class 类{
		friend 返回值 operator 运算符(参数列表);
	};
	返回值 operator 运算符(参数列表)
	{
		函数体;
	}		
}

{//成员函数运算符重载
	//成员函数:必须是对某一个对象进行调用
	注:
	不能放在private中
	少一个参数,变成this指针
	
	[], = 只能使用成员函数重载

	class 类{
			返回值 operator 运算符(参数列表)
	};
		
	返回值 类::operator 运算符(参数列表)
	{
		函数体;
	}

}

{//友元成员运算符重载函数
	+运算
友元函数:+运算符
	friend Integer operator + (const Integer &obj1, const Integer &obj2);
	Integer operator + (const Integer &obj1, const Integer &obj2)
	{
		Integer obj(obj1.val + obj2.val);
		return obj;
	}	

成员函数:+运算符
	Integer operator + (const Integer &obj);//少了自己本身
	Integer Integer::operator + (const Integer &obj)
	{
		Integer obj1(this->val + obj.val);
		return obj1;
	}
+=运算
	友元函数:+=运算符
		friend Integer &operator += (Integer &obj1, const Integer &obj2);
		//没有&:临时的局部对象,多加复制构造函数 Integer obj(obj1)
		//有&:不会分配新的空间,不会临时创建,不会创建一个复制构造函数
		//如果返回引用参数本身:用引用  如果返回obj1,obj2就用引用&
		Integer &operator += (Integer &obj1, const Integer &obj2)
		{
			obj1.val += obj2.val;
			return obj1;//引用表示obj1本身
		}	
	成员函数:+=运算符
		Integer &operator += (const Integer &obj);
		//成员函数:必须是对某一个对象进行调用
		Integer &Integer::operator += (const Integer &obj)
		{
			this->val += obj.val;
			return (*this);//引用表示*this本身
		}	
前++运算
	友元函数:前++运算符
		friend Integer &operator ++ (Integer &obj);
		Integer &operator ++ (Integer &obj)//改变obj的值,不能加const
		{
			++obj.val;
			return obj;
		}	
	成员函数:前++运算符
		Integer &operator ++ ();
		Integer &Integer::operator ++ ()
		{
			this->val++;
			return *this;
		}	
后++运算 
	友元函数:后++运算
		friend Integer operator ++ (Integer &obj, int);
		Integer operator ++ (Integer &obj, int)//后++
		{
			Integer obj1(obj.val);
			obj.val++;
			return obj1;	//表达式为之前的值
		}
	成员函数:后++运算 
		Integer operator ++ (int);
		Integer Integer::operator ++ (int)//后++------>int 只是一种格式,用却区分前++还是后++
		{
			Integer obj1(this->val);
			this->val++;
			return obj1;	//表达式为之前的值
		}	
成员函数[]运算 
	int operator [](int num);
	int Array::operator [](int num)
	{
		if(num < 0 || num > size - 1)
			return -1;
		return this->arr[num];
		//return arr[num];//也可以
	}

}

}

{//模板
模板是一种对类型进行参数化的工具,通常有两种形式:
函数模板
 函数模板针对仅参数类型不同的函数
类模板
 类模板针对仅数据成员和成员函数类型不同的类
注意:
模板的声明或定义只能在全局,命名空间或类范围内进行。即不能在局部范围,函数内进行,比如不能在main函数中声明或定义一个模板
{//函数模板 参数类型不一样的但是功能及函数名一致的函数
template
T add(T a, T b)
{
return (a+b);
}
//<>里面的参数是返回的是:typename T
cout << add(3, 4) << endl;
cout << add(3.1, 4.2) << endl;

		template 
		T add(T a, H b)
		{
			return (a+b);
		}
		//<>里面的参数是返回的是typename T, typename H
		cout << add(3, 6.6) << endl;
		cout << add(3.6, 6) << endl;
	}
{//类模板    成员属性的类型和成员函数的类不一样但是成员属性及函数一样的类
		声明数据类型参数标识符的关键字既可以用class也可以用typename
		
		注: 类模板实例化时必须指定类型
		 类成员如果在外部定义时需要加上模板声明
		 类外部定义时成员函数前面需要加上 Data :: ---表明是Data模板类的成员
		 
		 模板类的声明 与定义需要放在同一个文件中.h
}

{//友元函数模板
		如果一个类是模板类,又要实现运算符重载,一般的,运算符重载是友元函数		
}

}

{//异常
//为什么要用异常?
让一个函数(库)在发现自己无法处理的错误时抛出一个异常,然后调用者能够处理这个问题
C语言中处理异常采用goto语句。
//C++中异常的处理机制有三部分
try(检测错误) -----> throw(抛出错误) ------> catch(捕获错误)
}

{//智能指针
定义:是一个特殊的类模板,重载了“-> ”和 “* ”运算符,实现了自动回收机制
C++中有四种智能指针
//auto_ptr:----->在C++11总已经建议废弃使用,
//shared_ptr:共享指针
定义:被用来表示共享的拥有权。
也就是说, 当两段代码都需要访问一些数据, 而它们又都没有独占该数据的所有权( 从某种意义上来说就是该段代码负责销毁该对象)
shared_ptr是一种计数指针。 当引用计数变为0时, shared_ptr所指向 的对象就会被删除。
{
shared_ptr p(new Demo);
shared_ptr p = make_shared (Demo());
//make_shared会调用复制构造函数
}
//unique_ptr:
//weak_ptr:弱指针

}

{//c++中static
静态成员不与具体的对象关联也不能直接访问类的其他成员
里面没有this指针。
把一个类的成员说明为 static 时,这个类无论有多少个对象被创建,这些对象共享这个 static 成员
-------------->static使用之前必须要定义

静态成员函数里面没有this指针
静态成员函数不能访问非静态成员,可以访问静态成员

}

{//C++中const
1.成员函数
当某个成员函数不会修改类中的成员,就可以把这个函数设置为const成员函数
一般形式:返回值 成员函数名() const{
函数体;
}
const成员函数的声明与定义都需要加上const
const成员函数不能修改对象
const成员函数不能调用非const成员函数,但可以调用const成员函数
非const成员函数可以调用const成员函数

2.const对象
	当对象当中所有成员都不需要修改时,就可以用const修饰,避免不小心修改里面的成员
	一般形式:
		const  类 对象;
		类  const 对象;
		
		常对象只能访问常成员函数,不能访问非常成员函数。
		也就是说常对象只有构造析构函数与常成员函数才有意义
3.const成员变量	
	1.必须要初始化,且是在构造函数的初始化列表中进行
	2.非const成员函数可以使用const成员变量,但不能修改

}

{//STL
STL即标准模板库

STL主要是一些“容器”的集合,这些“容器”有list、vector、set、map,等等

STL的目的是标准化组件,这样就不用重新开发,可以使用现成的组件。

STL可分为六个部分:
	容器(containers)
	迭代器(iterators)
	空间配置器(allocator)
	配接器(adapters)
	算法(algorithms)
	仿函数(functors)
	
容器(containers)
	特殊的数据结构, 实现了数组、 链表、 队列、 等等, 实质是模板类
迭代器(iterators)
	一种复杂的指针, 可以通过其读写容器中的对象, 实质是运算符重载
算法(algorithms)
	读写容器对象的逻辑算法: 排序、 遍历、 查找、 等等, 实质是模板函数
空间配置器(allocator)
	容器的空间配置管理的模板类
配接器(adapters)
	用来修饰容器、 仿函数、 迭代器接口
仿函数(functors)
	类似函数, 通过重载( ) 运算符来模拟函数行为的类
组件间的关系
	container( 容器) 通过 allocator( 配置器) 取得数据储存空间, algorithm( 算法) 通过 iterator( 迭代器) 存取
	container( 容器) 内容, functor( 仿函数) 可以协助 algorithm( 算法) 完成不同的策略变化, adapter( 配接器) 可
	以修饰或套接 functor( 仿函数) 。

STL序列容器

	vector(相当于顺序表)
		 数组尾部添加或移除元素非常快速, 但是在中部或头部安插元素比较费时
	deque(双端队列)
		可以随机存取元素( 用索引直接存取) , 数组头部和尾部添加或
		移除元素都非常快速, 但是在中部或头部安插元素比较费时
	list(双向链表)
		不提供随机存取( 按顺序走到需存取的元素) , 在任何位置上执行插入或删除动作都非常迅速, 内部只需调整一下指针	

}

你可能感兴趣的:(笔记,C++,c++)