C++基础学习总结

来源:互联网 发布:粉丝应援 知乎 编辑:程序博客网 时间:2024/06/14 04:16

C++基础学习总结

 

0 概述

C++作为一门面向对象的编程语言,以其高效而又实用的特性,既可以进行过程化程序设计,又可进行面向对象的程序设计,是软件开发的直接工具。

1 C++C的比较

C++语言是在C语言的基础上进行了较大量的扩充和改进而得到的。它继承了C语言的全部内容,并在C语言的基础之上增加了面向对象编程的内容。因此C++并不是一种新的语言,也不是纯粹的面向对象程序设计语言,因此学过C语言的人只要了解了C++与C语言的区别,很快就可进入到C++的面向对象程序设计部分。

2 C++基础知识

2.1 函数

函数是一个命名了的的代码块,可以通过调用函数执行相应的代码。函数可以有0个或者多个参数,而且通常会产生一个结果。

(一)函数的声明

在C++中,函数原型就是函数的声明。所以,函数原型除了向用户说明如何使用一个函数以外,还告诉电脑存在这样一个可以使用的函数。

在声明一个函数的时候,参数是没有实际值的,只是起到一个占位的作用,所以称为形式参数,简称“形参”;在调用一个函数的时候,参数必须有一个确定的值,是真正能够对结果起作用的因素,所以称为实际参数,简称“实参”。

(二)函数的重写、重载、重定义

1. 重写 (override):

父类与子类之间的多态性。子类重新定义父类中有相同名称和参数的虚函数。

① 被重写的函数不能是static的。必须是virtual的。

② 重写函数必须有相同的类型,名称和参数列表 (即相同的函数原型)

③ 重写函数的访问修饰符可以不同。尽管virtual是private的,派生类中重写改写为 public,protected 也是可以的

2. 重载 (overload):

指函数名相同,但是它的参数表列个数或顺序,类型不同。但是不能靠返回类型来判断。

3. 重定义 (redefining):

子类重新定义父类中有相同名称的非虚函数(参数列表可以不同 ) 。

重写与重载的区别

1、方法的重写是子类和父类之间的关系,是垂直关系;方法的重载是同一个类中方法之间的关系,是水平关系。

2、重写要求参数列表相同;重载要求参数列表不同。

3、重写关系中,调用那个方法体,是根据对象的类型来决定;重载关系是根据调用时的实参表与形参表来选择方法体的。 

2.2

类是对某个对象的定义,它包含某个对象的名称、方法、属性和事件。

类成员有3种不同的访问权限:

1)公有(public)成员可以在类外访问。   

2)私有(private)成员只能被该类的成员函数访问。   

3)保护(protected)成员只能被该类的成员函数或派生类的成员函数访问。

数据成员通常是私有的,成员函数通常有一部分是公有的,一部分是私有的。公有的函数可以在类外被访问,也称之为类的接口。可以为各个数据成员和成员函数指定合适的访问权限。

封装就是通过权限来限制类中的代码外界无法看到更无法更改。值能通过接口来直接使用。类就像一个生产车间,外界的人只需知道丢进去什么材料(参数),然后这个车间产生出来的是什么(接口),而把生产过程封装了,你不知道是怎么生产的。

2.2.1 构造函数

每个类都分别定义了它的对象被初始化的方式,类通过一个或几个特殊的成员函数来控制其对象的初始化过程,这些函数叫做构造函数。构造函数的任务是初始化对象的数据成员,无论何时只要类的对象被创建,就会执行构造函数。

1. C++规定,每个类必须有构造函数,没有构造函数就不能创建对象。

2. 若没有提供任何构造函数,那么c++自动提供一个默认的构造函数,该默认构造函数是一个没有参数的构造函数,它仅仅负责创建对象而不做任何赋值操作。

3. 只要类中提供了任意一个构造函数,那么c++就不在自动提供默认构造函数。

4. 类对象的定义和变量的定义类似,使用默认构造函数创建对象的时候,如果创建的是静态或者是全局对象,则对象的位模式全部为0,否则将会是随机的。

例如下面的代码:

#include <iostream>  
using namespace std;    
class Student  
{  
    public:  
    Student()//无参数构造函数  
    {  
        number = 1;  
        score = 100;  
    }  
    void show();  
    protected:  
    int number;  
    int score;  
};  
void Student::show()  
{  
    cout<<number<<endl<<score<<endl; 
}  
void main()  
{  
    Student a;  
    a.show();  
    cin.get();  
}

 

执行结果:1

        100

在类中定义的和类名相同,并且没有任何返回类型的Student()就是构造函数。这是一个无参数的构造函数,它在对象创建的时候自动调用,如果去掉Student()函数体内的代码,那么它和C++默认提供的构造函数等价。构造函数可以带任意多个的形式参数,这一点和普通函数的特性是一样的!类成员的构造是按照在类中定义的顺序进行的,而不是按照构造函数说明后的冒号顺序进行构造的。

 

2.2.2析构函数

 

定义:析构函数也是特殊的类成员函数,它没有返回类型,没有参数,不能随意调用,也没有重载,只有在类对象的生命期结束的时候,由系统自动调用。 

析构函数与构造函数最主要大不同就是在于调用期不同,构造函数可以有参数可以重载!

如以上程序就可以用~Student()表示析构函数。

 ■ 析构函数函数名与类名相同, 紧贴在名称前面用波浪号~ 与构造函数进行区分,例如: ~Point();

 ■ 析构函数没有返回类型, 也不能指定参数,因此析构函数只能有一个,不能被重载;

 ■ 当对象被撤销时析构函数被自动调用, 与构造函数不同的是,析构函数可以被显式的调用,以释放对象中动态申请的内存。

如下代码所示:

#include <iostream> 

#include <cstring>    

using namespace std; 

class Book       

{

     public: 

     Book( const char *name )      //构造函数

{

      bookName =new char[strlen(name)+1];

      strcpy(bookName, name);

}

~Book()                 //析构函数

{

      cout<<"析构函数被调用...\n";

      delete []bookName;  //释放通过new申请的空间

}

void showName()

{

     cout<<"Book name: "<< bookName <<endl;

    }

     private:

     char *bookName;    

};

         int main()

        {

             Book CPP("C++ Primer");

             CPP.showName();

             return 0;

        }

 

编译运行的结果:

        Book name: C++ Primer

        析构函数被调用...

 

 

3 C++标准库

3.1 IO

IO库的分类

IO库大致可操作三类数据: 控制台流(stream) , 文件(file) , 字符串 (string)。

操作类型又可分三类:输入(in), 输出(out) ,输入与输出(in/out)。

基类及扩展类

a. ostream 是所有类型输出操作的基类

它扩展出两个子类: ofstream针对文件的输出操作类;ostringstream针对string的输出操作类

b. istream 是所有类型输入操作的基类

它扩展出两个子类: ifstream针对文件的输入操作类;istringstream针对string的输入操作类

c. ostream 和 istream共同扩展出类 iostream 它负责处理控制台stream的输入和输出

d. iostream 扩展出两个子类:stringstream专门处理string的输入输出; fstream专门处理文件的输入输出

3.2 容器

C++中的容器类包括“顺序存储结构”和“关联存储结构”,前者包括vector,list,deque等;后者包括set,map,multiset,multimap等。若需要存储的元素数在编译器间就可以确定,可以使用数组来存储,否则,就需要用到容器类了。

3.2.1 顺序容器

1、vector

连续存储结构,每个元素在内存上是连续的;

支持高效的随机访问和在尾端插入/删除操作,但其他位置的插入/删除操作效率低下;

2、deque

连续存储结构,即其每个元素在内存上也是连续的,类似于vector,不同之处在于,deque提供了两级数组结构,第一级完全类似于vector,代表实际容器;另一级维护容器的首位地址。

  这样,deque除了具有vector的所有功能外,还支持高效的首端插入/删除操作。

3、list

  非连续存储结构,具有双链表结构,每个元素维护一对前向和后向指针,因此支持前向/后向遍历。

  支持高效的随机插入/删除操作,但随机访问效率低下,且由于需要额外维护指针,开销也比较大。

4、vector与list 与 deque的比较:

 a、若需要随机访问操作,则选择vector;

 b、若已经知道需要存储元素的数目, 则选择vector;

 c、若需要随机插入/删除,则选择list

 d、只有需要在首端进行插入/删除操作的时候,才选择deque,否则都选择vector。

 e、若既需要随机插入/删除,又需要随机访问,则需要在vector与list间做个折中。

 f、当要存储的是大型负责类对象时,list要优于vector;当然这时候也可以用vector来存储指向对象的指针,同样会取得较高的效率,但是指针的维护非常容易出错,因此不推荐使用。

5、capacity与size

a、capacity是容器需要增长之前,能够盛的元素总数;只有连续存储的容器才有capacity的概念(例如vector,deque,string),list不需要capacity。

b、size是容器当前存储的元素的数目。

c、vector默认的容量初始值,以及增长规则是依赖于编译器的。

6、迭代器iterator

a、vector与deque的迭代器支持算术运算,list的迭代器只能进行++/--操作,不支持普通的算术运算。

3.2.2 关联容器

关联容器提供了基于KEY的数据的快速检索能力。元素被排好序,检索数据时可以二分搜索。STL有四种关联容器。当一个 KEY 对应一个 Value 时,可以使用集合(Set)和映射(Map);若对应同一 KEY 有多个元素被存储时,可以使用多集合(MultiSet)和多映射(MultiMap)。

标准关联容器总是保持排列顺序的,所以每个容器必须有一个比较函数(默认为less)。等价的定义正是通过该比较函数而确定的。相等一定等价,等价不一定相等。

每当你创建包含指针的关联容器时,容器将会按照指针的值(就是内存地址)进行排序,绝大多数情况下,这不是你所希望的。

如果你重视可移植性,就要确保set和multiset中的元素不能被修改。至少不能未经过强制类型转换(转换到一个引用类型const_cast<T&>)就修改。

如果你想以一种总是可行而且安全的方式来修改set、multiset、map和multimap中的元素,则可以分5个简单步骤来进行:

1. 找到你想修改的容器的元素。

2. 为将要被修改的元素做一份拷贝,在map和multimap的情况下,请记住,不要把该拷贝的第一个部分声明为const。毕竟,你想要改变它。

3. 修改该拷贝,使它具有你期望的值。

4. 把该元素从容器中删除,通常是通过erase来进行的。

5. 把拷贝插到容器中去。如果按照容器的排列顺序,新元素的位置可能与被删除元素的位置相同或紧邻,则使用“提示”(hint)形式的insert,以便把插入的效率从对数时间提高到常数时间。

3.2.3 适应器

适应器(Adaptor)是提供接口映射的模板类。适应器基于其他类来实现新的功能,成员函数可以被添加、隐藏,也可合并以得到新的功能。

(一)容器适配器

    要使用适配器,需要加入一下头文件:

    #include <stack>        //stack

    #include<queue>       //queuepriority_queue

 

下面的程序读入一系列单词存储在stack中,然后再显示输入的单词:

#include <iostream>

#include <stack>

#include <string>

using namespace std;

int main()

{

stack<string> words;

string str;

cout<<"Enter some words(Ctrl + Z to end):"<<endl;

while(cin >> str)

{

words.push(str);

}

while(words.empty() ==false)

{

cout<<words.top()<<endl;

words.pop();

}

return 0;

}

 

程序运行结果:

Enter some words(Ctrl + Z to end):

hello

world

(ctrl+z)

world

hello

 

(二)迭代适应器

a. 插入迭代器(Insert Iterator)

(1)向后插入(back_insert_iterator),在容器尾部插入

(2)向前插入(front_insert_iterator),在容器头部插入

(3)插入(insert_iterator),在容器中任一位置

STL 中提供了三个函数分别构造相应的插入迭代器:

(1)back_inserter

(2)front_inserter

(3)inserter

 

b. 逆向迭代器(Reverse Iterator)

逆向迭代器的递增是朝反方向前进的。对于顺序容器(vector, list 和 deque),其成员函数 rbegin()和 rend()都返回了相应的逆向迭代器。

4 类设计工具

4.1 重载运算与类型转换

4.1.2 运算符重载

运算符重载规则如下: 
①、 C++中的运算符除了少数几个之外,全部可以重载,而且只能重载C++中已有的运算符。 
②、 重载之后运算符的优先级和结合性都不会改变。 
③、 运算符重载是针对新类型数据的实际需要,对原有运算符进行适当的改造。一般来说,重载的功能应当与原有功能相类似,不能改变原运算符的操作对象个数,同时至少要有一个操作对象是自定义类型。 
不能重载的运算符只有五个:成员运算符“.”、指针运算符“*”、作用域运算符“::”、“sizeof”、条件运算符“?:”。

运算符重载为类的成员函数的一般语法形式为: 
函数类型 operator 运算符(形参表) 
{
  函数体; 
}

 

运算符重载为类的友元函数的一般语法形式为: 
friend 函数类型 operator 运算符(形参表) 
{
  函数体; 
}

其中,函数类型就是运算结果类型;operator是定义运算符重载函数的关键字;运算符是重载的运算符名称。 
当运算符重载为类的成员函数时,函数的参数个数比原来的操作个数要少一个;当重载为类的友元函数时,参数个数与原操作数个数相同。

原因是重载为类的成员函数时,如果某个对象使用重载了的成员函数,自身的数据可以直接访问,就不需要再放在参数表中进行传递,少了的操作数就是该对象本身。而重载为友元函数时,友元函数对某个对象的数据进行操作,就必须通过该对象的名称来进行,因此使用到的参数都要进行传递,操作数的个数就不会有变化。 
运算符重载的主要优点就是允许改变使用于系统内部的运算符的操作方式,以适应用户自定义类型的类似运算。

4.1.3 类型转换

C++中四种类型转换方式:

1.static_cast

最常用的类型转换符,在正常状况下的类型转换,如把int转换为float,

如:

int i;

float f;

f=(float)i;或者f=static_cast<float>(i);

 

2.const_cast

用于去除const属性,把const类型的指针变为非const类型的指针,

如:

const int *fun(int x,int y){}  

int *ptr=const_cast<int *>(fun(2.3))

3.dynamic_cast

该操作符用于运行时检查该转换是否类型安全,但只在多态类型时合法,即该类至少具有一个虚拟方法。dynamic_caststatic_cast具有相同的基本语法,dynamic_cast主要用于类层次间的上行转换和下行转换,还可以用于类之间的交叉转换。在类层次间进行上行转换时,dynamic_caststatic_cast的效果是一样的;在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全。如:

class C

{
  //…C没有虚拟函数
}
class T{
  //…
}
int main()
{
  dynamic_cast<T*> (new C);//错误
}
此时如改为以下则是合法的:
class C

{
public:
  virtual void m() {};// C现在是多态
}

 

 

4.2 模板编程

 a. 模板定义以关键字 template 开始,后接模板形参表,模板形参表是用尖括号括住的一个或多个模板形参的列表,形参之间以逗号分隔。 模板形参表不能为空。

       b.模板形参表很像函数形参表,函数形参表定义了特定类型的局部变量但并不初始化那些变量,在运行时再提供实参来初始化形参。

       c.模板形参可以是表示类型的类型形参,也可以是表示常量表达式的非类型形参。非类型形参跟在类型说明符之后声明,类型形参跟在关键字class 或typename 之后定义,例如,class T 是名为T 的类型形参,在这里class 和 typename 没有区别。

compare 的模板版本:

template <typename T>

     int compare(const T &v1, const T &v2)

     {

         if (v1 < v2) return -1;

         if (v2 < v1) return 1;

         return 0;

     }

模板实例化:

int main()

{

           compare(1, 0);        // ok: binds template parameter to int

           compare(3.14, 2.7); // ok: binds template parameter to double

           return 0;

}

 

 

5 总结

不管学习哪一门编程语言,实践永远是最重要的,C++也是,在理论知识的基础上,要多进行实践才会有所收获、有所进步。以前在使用C语言的时候只有面向过程的思维,在接触了C++后,需要转变为面向对象的思维,尽可能地去使用类来封装,这样也在一定程度上保证了安全性。

0 0
原创粉丝点击