C++与C语言的区别:
C语言是面向过程的,面向函数的封装和函数的顺序调用的过程;c++面向对象开发即面向类开发——一个结构体就是一个对象,包含行为(函数),和属性(变量);
C++能够对函数进行重载,可以使用同名的函数,功能变得更强大, c++引入了名字空间,可以是定义的变量名更多(提升变量符号的复用性);
c++可以使用引用传参,引用传参比起指针传参更加快,指针传参还需要传送,而引用传参不需要传送参数就能使函数共用一个变量;
c++使用了类,能够继承,继承使得定义相似的类时能够直接从上一层得到一些方法或变量来进行直接使用;
c++相比c语言功能强大的同时,也带来了更复杂多样的语法,这对于初学者来说是一个大的难点,并且c++的效率仍然不如c语言,c语言适合底层开发,c+t适合c/s的软件开发.(C++效率高:编写效率高,特效效果明显,运行效率高:不明显)
client /server。(偏向上位机和上位机应用程序开发)
c++语法经常体现了包含和被包含的特性;
基本框架:
#include<iostream>//c++标准库
Using namespace std;//将std命名空间打开
Int main()
{
Return 0;
}
//引入代码的头文件
Extern “C”{
//c代码
}
extern "C"的主要作用就是为了能够正确实现C++代码调用其他C语言代码。加上extern "c"后,会指示编译器这部分代码按C语言的进行编译,而不是C++的。
C++的标准输入输出流—cout,cin
输入(从键盘获取)cin>>(变量名);
输出(输出到屏幕)cout<<(要输出的变量)<<endl;//endl相当于C语言的\n
标准输入(standard input)与预定义的istream对象cin对应scanf标准输出(standard output)与预定义的ostream对象cout对应printf标准出错(standard error)与预定义的的ostream对象cerr对应 perror///特点:自适应数据类型,可以连续操作
变量的定义:
与C语言差不多,c++变量定义时初始化的格式是
Int val;//定义变量不初始化
int val(100);//定义变量并初始化
指针和动态内存:
指针变量
用来存储地址的变量,在C语言中,如果地址类型不一致,只会报警告,而在C++中会更为严格会直接报错,所以在C++中类型必须要一致。例如:int *p = (int *)0x12324565;
动态内存的申请和释放:
数据类型*变量名 = new数据类型;
Int *p = new int;
数据类型*变量名 = new数据类型(初始值);//申请内存空间的时候直接初始化
Int *p = new int(99);
数据类型*变量名= new数据类型[数据元素个数];
Int *p = new int[10];
释放格式:
Delete 指针变量;//释放单个对象
Delete []指针变量;//释放多个连续的对象
总结:
C语言中的malloc free是函数,C++语言中的new delete是运算符
new在申请内存的同时,还会调用对象的构造函数(初始化),而malloc只会申请内存;同样,delete在释放内存之前,会调用对象的析构函数,而free只会释放内存。
new的构造函数的作用是帮你初始化new出来的空间的值
delele的析构函数的作用是帮你释放new出来的空间
引入命名空间:
- :如果引入了某个命名空间,那么使用它的变量就不需要再写 命名空间::变量 了,直接写命名空间里面的变量即可;如:
Namespace:a{
Int a;
Int b;
}
Using namespace a;//引入命名空间
如果命名空间中存在变量与命名空间的命名一样,则使用是需要使用格式命名空间::变量,其他变量则至直接使用变量名。
- :引入命名空间中指定的变量
引入格式:using 命名空间::变量名
注意:当变量名与命名空间的名称一样是不可以单独引入
- :引入的两个命名空间中有相同变量
引入不同的命名空间中有相同的变量时不能直接写变量名字对其使用,会报二义性;使用格式为命名空间::变量名。
- :全局作用域—默认命名空间—匿名空间
全局作用域的名字空间是匿名的(成为默认的匿名空间),直接用::引用即可;命名空间的本质就是作用域,小的作用域覆盖大的作用域;同一等级的作用域不能有相同名字的作用域或变量名。一个程序只有一个匿名空间的时候::可以不写
匿名空间小结:匿名空间可以在其内部再定义命名空间;
匿名空间只有一个,它的::可以不写
如果出现多个同级作用域中出现相同的符号,那么不能直接使用需要指定对应的命名空间
引用
- 概念:
引用就是一个变量或者是常量的别名,对引用的操作与对变量直接操作完全一样;引用是一种关系型声明的类型,说明它跟别的变量的关系,它所声明的变量不占内存空间,简单的讲是已有变量的别名来说明跟已有变量的关系,所以引用变量的类型要跟已有变量的类型保持一致。在编程语言中类型的本质是固定内存大小的别名,变量的本质是连续内存大小的别名;在c++中,引入引用类型,可以通过reference(引用)给已有变量取别名,那么已有变量和别名都代表这同一块内存空间的别名,所以不会分配空间给引用变量。
- 定义格式:
数据类型 & 引用名 = 变量(注意:数据类型必须与变量类型保持一致,而且必须要初始化)
- 引用特点:
- &在定义的时候不是取地址运算符,而是起标识符的作用表示给某个变量或者常量起一个别名,---引用不是变量
- 在定义引用的时候,数据类型指的是目标变量或者常量的数据类型
- 在定义引用的时候必须要对其进行初始化
- 引用定义完成后,目标变量就相当于有了两个名称,即变量名和引用名,直接使用引用名相当于直接操作变量的内存空间
- 引用本身不占用存储单元,仅仅是作为变量的别名。对引用求地址就是对目标变量求地址即这两个是等效的
- 不能建立引用的数组。因为数组是一个由若干个元素组成的集合,所以无法建立一个由引用组成的集合,但是可以建立数组的引用,即一个数组都是引用。
- 如果引用的对象是常量,那么引用必须用const修饰
- 引用与常量
将一个引用设置为常量后,不能通过该引用修改数据,但仍可通过被引用的变量来改变;如果被引用的数据是常量,引用本身也必须是常量,例如:const int a = 10; const &ra = 10;
- 引用的应用
- 引用作为参数(值,地址,引用)
作为函数的参数(引用传递,可以在一个函数里修改另一个函数的值)
-
- 引用作为函数的返回值
格式:返回值数据类型 &函数名(形参列表){
}
特点:a以引用的形式返回函数的值,定义函数时需要在函数名前加&
b用引用返回一个函数值得最大好处是在内部中不产生被返回值得副本。
C不能返回局部变量引用,因为局部变量在函数调用结束之后内存空间就被释放了
D如果一个函数的返回值是一个引用,那么函数可以调用,可以作为=的左值,如:
Int & getdata()
{
Return val;
}
Int main()
{
Getdata() = 200;
}
C++中的const与c的区别
在c++中使用const定义一个变量后,是不会为它分配空间的,而是将数据写入对应的符号表位置,节省使用时对内存的读写操作,提高效率
常量折叠:
Const int a = 10;
Int *p = (int *)&a;
*p = 11;
经过上述操作输出a,a的值不会因为Int *p = (int *)&a;操作而改变,因为用到a时编译器不会让CPU去a对应得动态内存地址上获取数据的值,而是从符号表里面获取招惹就是c++的常量折叠。在进行*p操作,属于迫不得已需要使用a的内存空间买这时候才会分配一个内存空间给a。
函数的重载
1、概念
- 函数名称必须相同
- 参数列表必须不同(个数不同、类型不同、参数排列顺序不同等)
- 函数的返回类型可以相同也可以不相同。
- 仅仅返回类型不同不足以成为函数的重载
- 解决函数名字资源问题
- 函数调用的时候很方便,自动根据不同的参数调用不同函数(静态多态-编译时候多态)
- Fun();
- Fun(int a);
- Fun(char b);
- Fun(char *p);
多态:多态性可以简单地概括为“一个接口,多种方法”,程序在运行时才决定调用的函数,它是面向对象编程领域的核心概念。
函数默认参数(缺省实参)和占位参数
函数的默认参数
在声明函数的时候可以赋予函数参数默认值。所谓默认值就是在调用时,可以不写某些参数的值, 编译器会自动把默认值传递给调用语句中。
注意:
如果函数的声明和定义是分开的,必须在声明的时候设置默认值,不可以在定义的时候设置默认值; 将变量作引用类型参数的默认值,这时变量必须是已经声明且是全局变量
例如:
void Fun(int a,char c,const char * str_p = "Dante");
void Fun(int a,char c,const char * str_p)
{
cout<< "a:" <<a<< " c:" <<c<< " My name is" <<str_p<< endl;
}
使用时没有给第三个参数赋值时第三个参数的默认值为“dante”
如果默认参数有引用时,需要把引用放在参数列表的最后(默认向右原则)
占位参数
数:跟默认参数不同,在函数定义时,形参只写类型,不写形参变量名(位参数与默认参数结合起来使用,可以做到兼容旧版本代码,提高代码的规范性)
类与对象
概念
类就比如结构体类型 对象就比如结构体类型对应的变量 也就是说: 对象的抽象是 类 类的具体化(实例化)就是对象
类的成员权限和类的继承
- 类的成员权限
public 公有 公有段的成员是提供给外部的接口使用的
protected 保护 保护段成员在该类和它的派生类中可见 (派生类于类的继承有关)
private 私有 私有段成员仅在类中可见
注意:类中没有设置成员的属性,那么其默认的权限属性是:私有属性。 私有属性的成员,不能再类外使用同时它的派生类也不能使用
- 类的继承
类的继承有三种,分别是公有继承,保护继承和私有继承
派生类对象访问基类的成员的方法由访问基类成员的访问属性决定,访问属性的最下限取决于继承方式
派生类访问基类的基本方法: 访问属性为公有的:可以在派生的类内或者类外访问 访问属性为保护的:只能在派生的类内访问 访问属性为私有的:可以通过基类的公有方法和保护方法间接访问
如果派生类在继承基类的时候选择 公有继承(public)
派生类访问基类公有成员属于公有属性访问,可以直接在派生类的内部和外部使用
派生类访问基类保护成员属于保护属性访问,可以在派生类的内部使用,但是不能外部使用
派生类访问基类私有成员属于私有属性访问,不能直接访问,可以通过基类的公有方法和保护方法间接访问 --- 私有属性访问
- 如果派生类在继承基类的时候选择 保护继承(protected)
派生类访问基类公有成员属于保护属性访问,可以在派生类的内部使用,但是不能外部使用
派生类访问基类保护成员属于保护属性访问,可以在派生类的内部使用,但是不能外部使用
派生类访问基类私有成员属于私有属性访问,不能直接访问,可以通过基类的公有方法和保护方法间接访问 --- 私有属性访问
- 如果派生类在继承基类的时候选择 私有继承(private)
派生类访问基类公有成员属于私有属性访问,可以在派生类的内部使用,但是不能外部使用
派生类访问基类保护成员属于私有属性访问,可以在派生类的内部使用,但是不能外部使用
派生类访问基类私有成员属于私有属性访问,不能直接访问,可以通过基类的公有方法和保护方法间接访问 --- 私有属性访问
公有继承,派生类使用基类的公有成员 --- 派生类在使用基类的公有成员时,是public属性访问,所以可以再派生类的外面和里面访问
公有继承,派生类使用基类的保护成员,那么派生类访问基类的保护成员的访问属性是protected属性访问,只能在类内访问
公有继承,派生类使用基类的私有成员 --- 不能,只能间接
第一种:调用父类的公有函数去访问基类的私有成员
第二种:调用调用父类的保护函数去访问基类的私有成员
保护继承,派生类使用基类的公有成员 --- 派生类在使用基类的公有成员时,是保护属性访问,所以只能在派生类里面访问
保护继承,派生类使用基类的保护成员 --- 派生类在使用基类的保护成员时,是保护属性访问,所以只能在派生类里面访问
- 类的大小
1、类本身是一种数据类型,在没有定义对象(实例化对象)前是不占用内存空间的,定义对象的时候才会分配 空间
2、计算一个类的对象占用多少空间用sizeof(类名或对象) 1)类的内存空间大小是其数据成员(非静态-数据段)和虚表大小有关,跟函数成员无关
补充:C++三大特性 封装、继承、多态。而其中的多态,分为静态联编和动态联编, 在编译过程中进行联编称为静态联编, 编译器必须生成的程序在运行时选择正确的代码,称为动态联编。 虚函数是动态联编的关键,使用virtual关键字创建虚函数,在派生类中进行重写。而这里的虚表就存放的是指向虚函数首地址的指针。
2)如果一个类中没有数据成员(空类),也没有虚表,那么这个类的大小规定为1个字节
面试题:为什么空类的大小为1个字节?
空类内部不放任何成员,实际上大小一应该为0个字节。但是因为C++中空类可以实例化对象,有了对象就必须给其申请独一无二的内存地址, 但又因为该类是空类,并有没有具体的成员()需要申请空间,所以C++就给这个空类的实例化对象1个字节空间。
类的构造函数
概念:构造函数就是和类名一样,而且是没有返回值的函数。
如下所示:
class demo_class {
public: demo_class();//构造函数 也是类的成员,如果不指定属性,则默认为私有的
};
如果构造函数被你弄成了私有属性的(保护属性的也同理),那么编译器会报错,因为构造函数就是在实例化对象时,在类外通过实例化的对象去自动调用构造函数, 而构造函数是私有的,那么就出现了矛盾性。
特点:构造函数会在定义类对象是自动被调用,所以有自动初始化类成员数据的作用。 同时构造函数一般是公有的(不能是私有的和保护的)。 构造函数的特殊用法:初始化列表
拷贝构造函数
概念:
和构造函数一样,唯一不同的是拷贝构造函数有形参,该形参接收一个该类的对象。 具体如下所示:
class demo_class {
public: string string_data; demo_class();
demo_class(string string_data);
demo_class(demo_class const &tmp_dome);
};
作用:拷贝构造函数中的拷贝可以理解成赋值,把一个类对象赋值给另外一个对象。(简单点来说就是把形参的对象赋值给你定义的对象), 也就是初始化类对象的作用。
如果没有定义构造函数,编译器会自动生成一个空的构造函数
拷贝构造函数中的深拷贝和浅拷贝
浅拷贝:只拷贝对象本身空间里面的内容,编译器默认生成的拷贝构造函数 就是 浅拷贝 深拷贝:拷贝对象本身空间内容的同时,还要分配成员指向的堆空间并且进行拷贝
析构函数:
概念:析构函数的理解刚好和构造函数相反,他是在类的周期结束时会被自动调用(也就是类对象用完之后自动调用析构函数)。 析构函数是构造函数前面加个~。 详细一点应该这样说:当类离开了他的域或者delete释放了一个类的空间,析构函数就会自动被调用。 当delect一个类对象指针时,析构函数也会自动被调用 (类的对象,类的对象的指针,有结构体,结构体指针)
- 当类离开了他的域,析构函数就会自动被调用。
- 当delete一个类对象指针时,析构函数也会自动被调用
- new出来的堆空间中的类对象,不会因为程序退出而且自动调用析构函数
问题:派生类会继承基类的构造函数和析构函数?
因为本来派生类就有自己的构造函数和析构函数。不会继承基类的构造函数和析构函数,自会继承成员变量和普通的成员方法。