C++ 基础

C++与C的区别

C++包含C几乎全部功能。
C++比C多面向对象语言部分。
C++比C多泛型编程部分。
C++比C多STL部分。
C++头文件不带.h,.h不使用命名空间,故不用using namespace std,而不带.h的定义是放在std里,需要使用using namespace std,如果不用using,使用的时候就必须std::cout用法。
另外,C与C++中的string是定义字符串类,而C中的string.h 和C++中的cstring是定义字符串处理内容。

面向对象的特点

抽象、封装、继承、多态性。

程序的内存分配

C++程序的内存分为5个区,分别是堆、栈、自由存储区、全局/静态存储区和常量存储区。

  1. 堆区(heap):堆是操作系统中的术语,分配方式类似于链表,是操作系统所维护的一块特殊内存,用于程序的内存动态分配,C语言使用malloc从堆上分配内存,使用free释放已分配的对应内存。
  2. 栈区(stack):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率高但分配的内存容量有限。
  3. 自由存储区:自由存储区是C++基于new操作符的一个抽象概念,凡是通过new操作符进行内存申请,该内存即为自由存储区。
  4. 全局/静态存储区:全局变量和静态变量被分配到同一块内存中,在程序整个运行期间都存在,在以前的C语言中,全局变量又分为初始化和未初始化,C++取消了这个区分,共同占用同一块内存区。
  5. 常量存储区:这是一块比较特殊的存储区,他们里面存放的是常量(const),不允许修改。

符号常量误区

// #define容易出错的点,如下   
#define pi 3.14   
#define R a+b  
int a = 1, b = 2;
cout<<pi * R * R <<endl;
//输出的不是3.14 * (a+b) * (a+b),而是3.14*a + b*a + b。

函数重载

函数名相同,返回值类型(可相同)和参数列表要不同(包括数量和类型,至少有一个不同)。

函数模板

template< typename T > //模板声明,T为类型参数,typename可以换成class  
T function(T x, T y) {} //函数定义  
int a = 1,b = 1, c;  
c = function(a, b); //函数调用

函数参数默认值

函数默认值的定义,在函数声明时候带默认参数,先定义的话在定义时候给出默认值。
实参和形参的结合从左到右顺序进行,指定默认值的参数必须放在形参列表的最右端。
一个函数不能既作为有默认参数的函数,又作为重载函数。因为在调用的时候,参数的不规则性,无法判断是利用有默认参数的函数还是重载函数而产生二义性。
PS:默认参数函数的实参可与形参不对应,重载函数实参与形参个数要对应。

变量的引用

int &b = a; //此时&是引用声明符而不是取址符,不能理解为吧a的值赋给b的地址。
对变量的引用,并不开辟内存单元(指针是一个独立变量,占据内存空间)。对引用的声明,必须同时使之初始化(需要绑定同类型内存单元,故不能再作为其他变量的别名)。
指针与引用的区别:

int i = 3, j = 4;    
void swap1(int * p1, int  *p2) //函数定义  
{  
  int temp;  
  temp = *p1;  
  *p1 = *p2;  
  *p2 = temp;  
}  
Swap1(&i, &j); //函数调用

传送的实参是地址,用变量地址与形参对应,作为函数参数处理后,通过地址来访问实参变量,区别仅用实参变量作参数时的值传递。

void swap2(int &a, int  &b) //函数定义,带引用符  
{  
   int temp;  
   temp = a; //不带引用符  
z   a = b;  
   b = temp;  
}  
Swap2(i, j); //函数调用,不带引用符  

传送的是引用,用实参变量名与形参对应,作为函数参数处理后,通过地址来访问实参变量,区别仅用变量作参数的值传递和用地址作为参数的地址访问所需要的内存。

若定义在前,无声明时,要在定义中用引用来定义函数参数与函数体,函数有声明要在声明中说明函数参数为引用,然后在定义中用引用来定义函数内容。
PS:

  1. 不能建立void类型的引用 void &a = 9;
  2. 不能建立数组的引用 char c[6] = “hello”; char &r[6] = c;
  3. 可以将变量的引用的地址赋给一个指针,此时指针指向原来的变量
int &b = a;   
int *p = &b; //指针变量p指向a的引用b的地址,等同于int *p = &a  
  1. 可以建立指针变量的引用
int i = 5;  
int *p = &i;  
int * &pt = p;  
  1. 可以用const对引用加以限定,不允许通过引用改变该对象的值
int i = 3;  
const int &a = i;   
此时a = 3;不允许改变。 i = 3;允许被改变。
i = 5;  
此时i = 5, a = 5;      
  1. 可以用const从而用表达式或不同类型变量对引用进行初始化
int i = 5;  
const int &a = i + 3; //编译系统会生成一个临时变量来存放表达式的值  
double d = 3.14;  
const int &a = d; //编译系统会生成一个临时变量来转化变量的值  

作用域运算符

在mian函数中局部变量将屏蔽全局变量,此时全局变量表示为::a。

字符串变量

  1. 使用字符串变量要包含string头文件,#include。
  2. 字符串变随字符串长度动态改变,因为字符串变量存放的是字符串的指针,所以用sizeof求值为4个字节。
  3. 字符串常量以"\0"作为结束符,但将字符串常量存放到字符串变量中时,只存放字符串本身而不包括"\0"。
  4. 定义一个字符串变量可以直接赋值。
string string;  string = "Hello";

而字符数组不可以直接赋值,区别字符数组的初始化。

char string[10]; string = “Hello”; //error

因为字符串中串名指整个串的首地址,而字符数组中数组名代表元素首地址,类型不同。
5. 字符数组操作时候使用字符串函数,而字符串变量操作时可以直接使用运算符。

内存操作

  1. new 类型 [初值],[]为可选项,分配数组空间不能指定初值,即不能初始化。
new int;  //开辟一个存放整数的空间  
new int (100); //开辟一个存放初值为100的整数的空间  
new int [10]; //开辟一个存放大小为10的整型数组空间  
new int [5][4]; //开辟一个存放大小为5*4整型数组空间    
float * p = new float(3.14); //开辟存放实数的空间,初值3.14,并返回的指针赋给指针变量p   
  1. delete [] 指针变量,撤销指针指向的new开辟的内存空间
    PS:new和delete属于运算符,执行效率比函数要高。

类封装与信息隐蔽

类库一般包含类声明头文件和已经编译过的成员函数的定义。
这样类声明头文件就称为用户使用类库的有效方法和公用接口,编译后的函数目标文件又实现了信息隐蔽。
另外,如果函数定义也放在头文件中,每一次使用类定义头文件,定义函数的内容都会被加载一遍,导致重复编译。

类的数据与函数、对象与指针

  1. 结构体成员不声明public或private,默认为public,而类成员则为private。
  2. 类函数
    1)类函数在类体中进行声明,在类外定义。类体定义要在函数定义之前。
    定义必须在函数名前加上类名和作用域限定符::,如Student::display();
    这样定义可以实现接口和类的实现相分离,隐藏了执行的细节。
    2)在类体中定义的函数(不包含循环等控制结构),将作为内置成员函数,如果是类外定义,则可使用inline声明为内置函数,但是要求类的声明和成员函数的定义都放在同一个文件中。
    内置函数省去了函数调用过程如保留返回地址等处理,而是把函数代码嵌入函数调用点,减少调用成员函数的开销,适合规模较小调用频繁的类成员函数。
    3)类的成员函数的代码段的存储都不占用对象的存储空间,而是在对象的公共存储空间。
    4)类成员初始化分别有函数体内赋值对成员初始化或参数初始化表对成员初始化,但是如果成员是数组的话,应该在函数中进行初始化。
    全部参数指定默认值的构造函数属于默认构造函数,并不能再定义重载构造函数。一般不应同时使用构造函数的重载和有默认参数的构造函数,因为有时候会有二义性。
    5)类的对象数组,有多少个数组就调用多少次构造函数。
Student::Student(int = 100, int = 100, int = 100);     
Student std[3] = {1, 2, 3}; //每个对象的第一个参数分别为1,2,3  
Student sdt[3] = { Student(1, 2, 3), Student(1, 2, 3), Student(1, 2, 3) } //为每一个对象初始化    

PS:所提供实参不能超过对象数组元素个数
6)指向成员函数的指针,对比指向普通函数指针和指向成员函数的指针

//普通函数:  
void (* p)( ); //指向void型函数的指针变量  
p = fun; //将fun函数入口地址给指针变量p  
(* p)( ); //调用fun函数 

//成员函数:  
void (Time:: * p)(); //定义指向Time类内void型成员函数指针    
p = &Time::get(); //使指针指向Time类内的成员函数get()    
p -> get(); //调用成员函数get()  
//void (Time:: * p)()中的()不能省略,即void Time:: * p()。     
//()优先级高于*,相当于void Time :: * ( p() ), void Time :: *即void *,返回类型变为void型指针。
*/   

类的公用数据的保护

  1. 常对象
    const 类名 对象名(实参表); //定义对象要有初值使之初始化,并且不能被修改。
    除了系统调用的隐式的构造函数和析构函数,常成员函数是常对象的唯一对外接口。
  2. 常成员函数
    void get(int, int) const; //可引用常对象的数据成员但是不可以修改
    常成员函数可引用任何当前对象的数据成员值但不能改变值,不能调用非const成员函数。
  3. 可变对象成员
    mutable int count; //这样可以用常成员函数进行修改值
  4. 常对象成员
    const int count; //只能用参数初始化表的构造函数进行初始化,不能改变值
    常数据成员可以被非const对象引用,但是不管什么函数都不可以改变其值。
  5. 常变量的指针 const 类型名 *指针变量名
    指向const型变量的指针可以指向const型变量也可指向非const型变量,但是不能通过该指针改变值,因为在访问期间具有常变量的特征。
    指向非const型变量的指针只能指向非const型变量,故const型变量只可被指向const型变量的指针指向。
    在函数中,当形参为指向const型变量的指针,实参可以为const型变量也可是非const型变量,但是不可以改变值。形参为指向非const型变量的指针,实参只能是非const型变量。指向对象同理。
    PS:当希望调用函数时对象的值不被修改,就应当把形参定义为指向常对象的指针变量,同时用对象的地址作为实参,对象可以是const或非const。
数据成员非const的普通成员函数const成员函数非const的普通指针const指针
非const的普通数据成员可以引用,也可以改变值可以引用,但不可以改变值可以指向,也可以通过指针改变值可以指向,不可以通过指针改变值
const数据成员可以引用,但不可以改变值可以引用,但不可以改变值不可以指向可以指向,但不可以改变值
const对象不可以引用可以引用,但不可以改变值可以指向,可通过指针调用const方法可以指向,可通过指针调用const方法

类对象的赋值和复制,静态数据成员与函数,类的友元和模版

  1. 类的赋值 对象1 = 对象2; //对象要属于同一个类
    类的数据成员不能包括动态分配的数据,否则在赋值时可能出现严重后果,当在某一方释放动态存储空间,将对另一方造成未知影响。
  2. 类的赋值 类名 对象1(对象2); //复制过程会有一个复制构造函数被调用
    类的赋值也属于类的复制。
  3. 静态数据成员 为各对象所共有,内存只占一份空间,所有对象的值都一样。
    在为对象分配空间不分配静态数据成员的内存,而是在定义类的时候分配空间。
    静态成员只能在类体外进行初始化,形式为 数据类型 类名::静态数据成员名 = 初值;
    不能用参数列表初始化对静态数据成员进行初始化。
    公用的静态数据成员在类外可通过类名或对象名引用,而私有的只能使用公用成员函数调用。
  4. 静态成员函数 同静态数据成员一样不是对象的一部分,调用可以通过类名或对象名。
    静态成员函数不指向某一对象,所以没有this指针,无法对一个对象中的非静态成员进行默认访问,即不能访问本类中的非静态成员。
    所以静态成员函数主要用来访问静态数据成员,如果一定要引用非静态成员,可以用对象名加成员运算符“.”进行引用,但是不建议这样做,逻辑不够清楚,容易出错。

提前声明类

在正式声明一个类之前,先声明一个类名,表示此类将在稍后进行声明,比如Class Time;
此时不能定义该类的对象,因为定义对象要为对象分配存储空间,在正式声明类之前,编译系统无法确定应为对象分配多大的空间。
但是在对一个类进行提前声明后,可以用该类的名字去定义指向该类型对象的指针变量或对象的引用。

类的模版

template <class numtype>  //声明一个模版,虚拟类型名为numtype  
class Example  //类模版名为Example  
{  
  public:  
    Example(numtype a, numtype b)  //定义构造函数    
    {x = a; y = b;}    
    numtype max()      
    { return (x > y ? x : y;}  
    numtype min()  
    { return (x < y) ? x : y;}    
  private:  
    numtype x, y;  
};  
Example <int> exam(3, 7);  //定义对象,类模版名 <实际类型名> 对象名(参数表); 

如果要在类模版外定义类成员函数,如下:  
template <class numtype> //tamplate <class 虚拟类型参数>  
/* 函数类型 类模版名 <虚拟类型参数>::成员函数名(函数参数列表) {…} */  
numtype Example <numtype>::max()   
{ return (x > y ? x : y;} 

PS:模版可以有层次,一个类模版可以作为基类,派生出派生模板类。

运算符重载

函数类型 operator 运算符名称(形参表)  //函数名为operator 运算符名称  
{
  对运算符的重载处理
}  

不能重载的运算符只有5个:
.(成员访问运算符,保证访问成员的功能不被改变)
*(成员指针访问运算符,保证访问成员的功能不被改变)
::(域运算符,运算对象是类型而不是变量和表达式)
sizeof(长度运算符,运算对象是类型而不是变量和表达式)
? :(条件运算符,唯一的三目运算符,重载复杂无意义)

  1. 用于类对象的运算符一般要重载,但=和&不必重载,系统已扩展了其功能到类对象中。
  2. 重载需要注意,不能改变运算符的运算对象(操作数)的个数、不能改变优先级别、不能改变结合性、不能带有默认参数(有默认参便影响操作数个数)。
  3. 另外,重载的运算符还要求和用户自定义类型的对象一起使用,其参数至少有一个是类对象或对象引用,以防止用户修改用于标准类型数据的运算功能。
  4. 当运算符重载函数作为成员函数,可以通过this指针自由地访问本类的数据成员,因此可以少写一个函数参数。但是要求运算表达式中第一个参数(即运算符左侧的操作数)是一个类对象(必须通过该类对象去调用该类的成员函数),而且与运算符函数的类型相同(返回值和函数类型相同,结果才有意义),当需要在左侧的操作数是标准型变量,运算符函数只能作为非成员友元函数。
  5. C++规定,赋值运算符=、下标运算符[]、函数调用运算符()、成员运算符->必须作为成员函数。流插入<<和流提取>>、类型转换运算符不能定义为类的成员函数,只能作为友元函数。
  6. 一般将单目运算符和复合运算符重载为成员函数,而将双目运算符重载为友元函数。
  7. 重载流运算符
istream & operator >> (istream &, 自定义类 &) //函数类型和第一个参数必须为istream &  
ostream & operator >> (ostream &, 自定义类 &) //函数类型和第一个参数必须为ostream &  

PS:重载流运算符函数类型为istream &或ostream &,且由于第一个参数已规定,用流对象而不是自定义对象,为了返回流的当前值以便连续输出,所以只能用友元函数进行重载。 8. 转换构造函数,其他数据类型转化成类的对象
先声明一个类,在这个类中定义一个只有一个参数的构造函数,参数的类型是需要转换的类型(可以为一个类,即进行类转换),在该类的作用域可以使用:类名(指定类型的数据),来进行转换,拥有转换构造函数的数据成员必须是公有成员,否则不能被类外引用。
PS:转换构造函数只能有一个参数,如果有多个参数,就不是转换构造函数。
9. 类型转换函数,类的对象转化成其他数据类型
如果在类中定义转换函数:

operator 类型名()   //函数名 operator 类型名  
{
  实现转换的语句
} 

可以由对象引出一个新数据,不指定函数类型,也没有参数。只能作为成员函数,因为转换主体是本类对象,此时类对象原有意义不改变,只添加了转换后的数据意义在必要时使用。
PS:在遇到重载问题时,先检查运算符重载,再检查类型转换,接着再检查转换构造函数,最后重新检查一次运算符重载。

继承与派生

公用继承

基类的公用成员和保护成员在派生类中仍然保持公用成员和保护成员的属性,而基类的私有成员在派生类中并没有成为派生类的私有成员,只有基类的成员函数可以引用基类的私有成员,而派生类成员函数不能引用基类的私有成员。

私有继承

基类的公用成员和保护成员在派生类中的访问属性被更改为私有,而基类的私有成员在派生类中依旧不可访问,只有通过基类的成员函数进行访问。
不能在派生类外通过派生对象调用基类的公用成员函数(此时函数为私有),但是可以在派生公用成员函数中调用基类的公用成员函数,从而达到访问基类中的私有成员。
对于不需要再向下继承的类可以采用私有继承方式封闭起来,使得更下一层的派生类无法访问任何成员。

保护成员和保护继承

保护成员属性等价于私有成员,但是保护成员可以被派生类的成员函数引用。
在基类中声明了私有成员,任何派生类都不能访问它们,而声明了保护成员,派生类可以访问它们,因此意味该类很可能作为基类。
在直接派生中,私有派生和保护派生作用相同,类外不能访问任何成员,在派生类中可以通过成员函数访问基类中的公用成员和保护成员。但是继续派生,私有基类的私有成员将变为不可访问,而保护基类的保护成员可以被新派生类的成员函数访问。

派生类的构造函数

  1. 派生类构造函数将先调用基类构造函数。
  2. 再调用子对象数据成员构造函数。
  3. 最后按派生顺序执行派生类构造函数。
  4. 析构函数与上述顺序相反。

派生类构造函数名(总参数表): 基类构造函数名(参数表)*N, 子对象构造函数名(参数表) //此处是调用基类构造函数(针对上一层)和子对象构造函数,因此参数表内是实参而不是形参
{派生类中新增数据成员初始化语句} //N代表虚基类构造函数(放最前面)及多重继承的各基类的构造函数
如:

Student1(int n, string nam, char s, int a, string ad) : Student(n, nam, s)  
{age = a; addr = ad;}  
  or  
Student1(int n, string nam, char s, int a, string ad) : Student(n, nam, s),  age(a), addr(ad)  
{}  

虚基类

在多继承中,新派生类中涉及到的共同的基类只保留一份成员。
对所有的直接派生类都应声明为虚基类,而且所有声明了虚基类的派生中都要再构造函数对虚基类进行初始化。
class 派生类名 : virtual 继承方式 基类名

多态性和虚函数

多态性

向不同对象发送同一个消息,不同的对象在接收时会产生不同的行为(即方法),也就是说每个对象可以用自己的方式去响应共同的消息。
静态多态性的表现是相同的符号和方法可以用实现不同的内容,及函数重载和运算符重载。
动态多态性的表示是使用虚函数,即在基类声明函数是虚拟的,在派生类中才正式定义函数内容,使得同一函数在不同对象中有不同内容。

虚函数

虚函数是允许在派生类中重新定义与基类同名的函数,并且通过基类指针或引用来访问基类和派生类中的同名函数。
本来,基类指针指向基类对象,如果用基类指针指向派生类对象,则自动进行指针类型转换,此时指针将指向派生对象中的基类部分,当用基类指针调用派生类中继承而来的成员函数时,能实现的是基类中的成员函数的功能。
1.在基类中用virtual声明成员函数为虚函数,在类外定义虚函数时不需再加virtual。
2.在派生类中重新定义函数内容,函数名、函数类型、函数参数名和类型不能改变。
3.当一个成员函数被声明为虚函数时,其派生类中的函数也为虚函数,因此在派生类中重新声明该函数可以不加virtual,习惯上加上会更清晰。
4.定义一个指向基类对象的指针变量,并使它指向同一类族中需要调用该函数的对象,通过指针变量可以调用相应对象的虚函数。
纯虚函数:virtual 函数类型 函数名(参数列表)=0; //没有函数体,=0是为了告诉系统这是一个虚函数
使用虚函数,需要关注该函数所在的类是否会作为基类,该函数是否在继承后功能会被更改,派生类是否会用到该函数,使用该函数是否通过基类指针或引用去访问。

虚析构函数

当使用指向基类的指针变量去指向new声明的临时对象,此时delete释放该指针的所指向的空间,只会执行基类的析构函数,派生类的析构函数是不执行的。故此我们需要将基类的析构函数声明为虚析构函数,使得delete运算会调用对应对象的析构函数。
构造函数不能声明为虚函数,这是因为执行构造函数时类对象还未完成建立过程,涉及不到把函数与对象绑定的过程。

输入输出流

程序的输入指的是从输入文件将数据传送到内存单元,程序的输出指的是从内存单元中将数据传送到输出文件。

标准的输入输出(标准I/O)

对系统指定的标准设备的输入输出,即从键盘鼠标输入数据,输出到显示器屏幕。

文件的输入输出(文件I/O)

以外存为对象进行输入输出,例从磁盘文件输入数据,数据输出到磁盘文件。

字符串输入输出(串I/O)

对内存中指定的空间进行输入输出,通常指定一个字符串数组作为存储空间来存储任何类型的信息。
ios,抽象基类,声明头文件:iostream
istream,通用输入流和其他输入流的基类,声明头文件:iostream
ostream,通用输出流和其他输出流的基类,声明头文件:iostream
iostream,通用输入输出流和其他输入输出流的基类,声明头文件:iostream
ifstream,输入文件流类,声明头文件:fstream
ofstream,输出文件流类,声明头文件:fstream
iofstream,输入输出文件流类,声明头文件:fstream
istrstream,输入字符串流类,声明头文件:strstream
ostrstream,输出字符串流类,声明头文件:strstream
iostrstream,输入输出字符串流类,声明头文件:strstream

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值