C++知识点⼤汇总
概述
1、1980年贝尔实验室 Bjanre Stroustrup(⽐雅尼·斯特劳斯特鲁普)对C改进与扩充最初称为“带类的C”,(c with classes). 1983年正式命名为C++
2、
C++是C的改进与扩充。
C++包括C的全部属性、特征、优点,是在C的基础上的改进与扩充。
C++包括过程性语⾔和类部分。
C++是混合型语⾔,即是过程型的,⼜是⾯向对象型的。
3、“⾯向过程”是⼀种以事件为中⼼的编程思想。功能分解、⾏为抽象的抽象编程。
4、⾯向对象程序设计的基本特征:
(1)对象:
数据属性(静态)、⾏为属性(动态);
实现封装与数据隐藏;
对象是对类的实例化。
(2)继承:派⽣其他类
(3)多态:同⼀个操作在不同的类上有着不同⾏为
5、编译过程可分为三个⼦过程:预处理过程、编译过程、连接过程
函数
内联函数
内联函数也称内嵌或内置函数,它的语法格式与普通函数⼀样,只是在函数原型或函数定义标题头之前加上关键字inline。
inline int isNumber (char);
inline int isNumber (char ch)
{
return(ch>='0' &&ch<='9')?1:0;
}
使⽤内联函数可以省去函数调⽤所需的建⽴栈内存环境,进⾏参数传递,产⽣程序转移的时间开销。内联函数应是使⽤频率⾼,代码却很短的函数。
内联函数的函数体限制:
内联函数中,不能含有switch和while。不能存在任何形式的循环语句
递归函数不能⽤来做内联函数。
内联函数中不能说明数组。否则,按普通函数调⽤那样产⽣调⽤代码。 (内联函数是建议性,不是指令性)
内联函数只适合于1-5⾏的⼩函数
类结构中所有在类内部定义的函数,都是内联函数。
内联函数中不能有过多的条件判断语句
不能对函数进⾏取址操作
重载函数
实现函数重载的条件:
同⼀个作⽤域
参数个数不同
参数类型不同
参数顺序不同
匹配重载函数的顺序:
(1)严格匹配
(2)内部转换(相容类型匹配)
(3)通过⽤户定义的转换寻求⼀个匹配。
C++⽤名字粉碎(name mangling)(名字细分、名字压轧)的⽅法来改变函数名。
返回值类型不能够作为重载依据(区分、细分重载)
默认参数的函数
1、当⼜有声明⼜有定义时,定义中不允许默认参数。若只有定义,则默认参数才可出现在函数定义中。
#include <iostream>
using namespace std;
void fun(int a = 20);
void fun(int a = 20)      //函数定义处不允许默认参数,应改为 void fun(int a){};
{ cout << "你好" << endl;
}
int main() {
fun(); return0;
}
#include <iostream>
using namespace std;
void fun(int);  //函数声明
int main(){
fun();
return0;
}
void fun(int a = 20) //函数定义处不允许默认参数,应改为 void fun(int a){};
{
cout << "你好" << endl;
}
2、⼀个函数中可以有多个默认参数,默认参数应从右⾄左逐渐定义,当调⽤函数时,从左向右匹配参数。
void foo(int a, int b = 0, bool c);    //fail,b不是最后⼀参数,只要有⼀个参数是默认参数,它后⾯的所有参数都必须是默认参数
3、默认值可以是全局变量、全局常量、函数。不可以是局部变量。因为默认值是在编译时确定的,必须是静态确定的。
4、默认参数不能⽤于细分重载函数。
void func(int,int);
void func(int=3,int=4);
//redefinition of 'void fun(int, int)'
void func(int);
void func(int,int=4);
/
/call of overloaded 'fun(int)' is ambiguous
5、函数的定义和声明中,都可以省略形参名
void print (int ,int);
void print(int a,int){
cout<<a<<endl;
}
void func(){
}
程序运⾏时的内存布局
代码区:存放程序的执⾏代码(各个函数的代码块)
全局变量区:放程序的全局数据和静态数据
堆区:存放程序的动态数据malloc free new delete
栈区:存放程序的局部数据(各个函数中的数据)
指针和引⽤
指针运算符
“ * ”称为指针运算符(间接访问运算符),表⽰指针所指向的变量的值。⼀元运算符
“ & ”称为取地址运算符,⽤来得到⼀个对象的地址。⼀元运算符。
数组名是个指针常量,int *const p,指针⾃⾝的值是⼀个常量,不可改变
void 指针与NULL指针值
(1)void
void 指针(void *p) 空类型指针不指向任何类型,仅仅是⼀个地址。
不能进⾏指针运算,也不能进⾏间接引⽤。
其他类型指针可以赋值给空类型指针
空类型指针经显⽰转换后⽅可赋给其他指针。
(2)NULL
NULL是空指针值,不指向任何地⽅。
任何类型的指针都可以赋该值。
指针和常量
(1)使⽤const 说明常量:int const x=2; 或const int x=2;
常量定义时,应被初始化。 const int i;(错误)
构造函数初始化时必须采⽤初始化列表⼀共有⼏种情况,
1. 需要初始化的数据成员是对象(继承时调⽤基类构造函数)
2. 需要初始化const修饰的类成员
3. 需要初始化引⽤成员数据
4. 类中含有对象成员,但其类型中,没有⽆参构造函数,必须在初始化列表中指明相应的有参构造函数。
5. 基类中缺少⽆参构造函数,必须在初始化列表中指明基类的有参构造函数。
(2)指针常量:在指针定义语句的指针名前加const, 表⽰指针本⾝是常量。 int a; int* const p=&a; 定义时必须初始化指针值p不可以修改,指针指向的内容可以修改。即p是常量,不可以作为左值进⾏运算,*p可以修改,p不可以修改
int *const q=&c;// 错误(不允许外界提供修改常量的漏洞)
(3)常量指针:在指针的定义类型前加const,表⽰指向的对象是常量。如const int *p或int const *p; 均可。以上定义表明,*p是常量,不能将*p作为左值进⾏操作。定义指向常量的指针时,定义时不必须先初始化。*p不可以修改,p可以修改
(4)指向常量的指针常量(常量指针常量)形式: const int *const p=&a;定义时必须初始化 p与*p都是常量。他们都不能作为左值进⾏操作
(5)常量的特殊⽤法:int f(int b) const;
(6)重载和const形参
void f(int* p);
void f(const int* cp);
//有效重载,是不是指向const对象
void f(int* p );
void f(int * const pc);
//⽆效重载,重定义不能基于指针本⾝是否const 实现重载
(7)new和delete进⾏动态内存分配和释放
int *p; p=new int(100);//动态分配⼀个整数并初始化
int *p;p=new int[10];//分配⼀个含有10个整数的整形数组   delete[ ] p; //删除这个数组
使⽤new较之使⽤malloc()有以下的⼏个优点:
new⾃动计算要分配类型的⼤⼩,不使⽤sizeof运算符,⽐较省事,可以避免错误。
⾃动地返回正确的指针类型,不⽤进⾏强制指针类型转换。
可以⽤new对分配的对象进⾏初始化。
不⽤使⽤头⽂件声明(malloc.h),更简洁。
new操作符若申请成功,返回⾸单元地址;否则返回NULL值。
函数指针和指针函数
(1)指针函数:返回指针值的函数。
int* f(int a);
(2)函数指针:指向函数地址(程序区)的指针。与函数名等价的指针。函数名是指向函数的指针常量。//类型 (*变量名) (形参表);
//()的优先级⼤于*
int f1(int n); 
isnumber函数的使用方法及实例
int (*pf1)(int n); 
pf1=f1; //pf1是与f1等价的函数指针
(3)通过函数指针来调⽤函数:
int add(int a,int b){return a+b;} 
int main() 
  { 
  int (*p)(int,int); 
  p=add;                        //p是与add等价的函数指针 
 cout<<add(3,5); 
 cout<<(*p)(3,5);     //四种调⽤形式效果等价
    cout<<p(3,5); 
    cout<<(*add)(3,5);
return0;
    } 
 //结果:8888 
(4)函数指针作函数形参:
例:计算以0.10为步长,特定范围内的三⾓函数之和。
#include <iostream>
using namespace std;
int sum(int a, int b){
return a + b;
}
int sub(int a, int b){
return a - b;
}
int get(int (*p)(int a, int b), int m, int n){
return p(m, n);
}
int main(){
cout << "sum=" << get(sum, 3, 3) << endl;
cout << "sub=" << get(sub, 3, 3) << endl;
return0;
}
/
/sum=6
//sub=0
(5)⽤typedef 来简化函数指针
typedef int (*FUN)(int a,int b);
int f(int,int);
FUN funp=f;
//FUN 不是指针变量,是指针类型名。
typedef  int FUNC(int,int); //先定义函数类型
FUNC* funp=f;
例⼦:
#include <iostream>
using namespace std;
int sum(int a, int b){
return a + b;
}
int sub(int a, int b){
return a - b;
}
int get(int (*p)(int a, int b), int m, int n){
return p(m, n);
}
typedef int (*Func)(int a, int b);
typedef int Func2(int a, int b);
int main(){
Func func = sum;
Func2 *func2 = sub;
cout << "sum=" << get(func, 3, 3) << endl;
cout << "sub=" << get(func2, 3, 3) << endl;
return0;
}
(6)函数的返回类型可以是函数指针
typedef int (*SIG)  ();
typedef void (*SIGARG)  ();
SIG signal (int,SIGARG);
引⽤
(1)为⼀个变量、函数等对象规定⼀个别名,该别名称为引⽤。
(2)声明引⽤:
int& ir=i;            //定义引⽤ir作为对象i的别名
声明引⽤,不为之分配内存空间。
(3)引⽤必须初始化。引⽤⼀旦被声明则不能再修改.
int  j,k;
int &s=j;    int &s=k;  //(错误)
int & i;                  //错误
extern int&  r3          //ok,r3在别处初始化
void &a=3;//error
//void 本质不是⼀个类型,只是在语法上相当于⼀个类型,没有该类型的对象。
(4)形参和实参结合规则: 
形参为引⽤时,凡遇到形参(引⽤)的地⽅,全部⽤实参(对象)来代替。
可读性⽐指针传递好(与传值⽅式的调⽤可读性相同,性能却强于传值⽅式)
可使⽤引⽤传递从函数返回多个值(指针和引⽤都可以)
(5)引⽤和指针的关系
指针是个变量,可再赋值;⽽引⽤建⽴时必须进⾏初始化并且决不会再关联其它不同的变量。
指针操纵两个实体(指针值、指向的值);引⽤只能操纵⼀个实体。
引⽤在内部⽤指针实现,被看成是指针常量,不能操作⾃⾝的地址值,只能访问所指向的实体。
实际上“引⽤”可以做的任何事情“指针”也都能够做,为什么还要“引⽤”?
答案是:“⽤适当的⼯具做恰如其分的⼯作”。指针能够毫⽆约束地操作内存中的东西,尽管指针功能强⼤,但是⾮常危险。引⽤是指针出于安全考虑的替代品。⾼级编程多⽤引⽤,低级编程多⽤指针,也是基于安全考虑。
在以下情况下你应该使⽤指针:
⼀是你考虑到存在不指向任何对象的可能(在这种情况下,你能够设置指针为空)
⼆是你需要能够在不同的时刻指向不同的对象(在这种情况下,你能改变指针的指向)。
如果总是指向⼀个对象并且⼀旦指向⼀个对象后就不会改变指向,那么你应该使⽤引⽤。
(6)⽤const 限定引⽤
int  i;  const int& p=i;
不能通过引⽤对⽬标变量的值进⾏修改,保证了引⽤的安全性。
注意:c++不分变量的const引⽤,和const变量的引⽤,因为引⽤本⾝就不能重新赋值,使它指向另⼀个变量。即没有const int const &a=1, 只有const int &a=1
(7)引⽤的使⽤-⽤引⽤返回值