C++基础入门-------从C到C++

本文是对C++的一些知识点总结以及自己的理解,建议是对于C有较好的理解或者是学过一些C++的同学使用,可以加深自己的理解!

一、 理解C++的类和对象

相信大家都认识C语言中的结构体这个数据结构,而C++ 中的类(Class)可以看做C语言中结构体(Struct)的升级版。结构体是一种构造类型,可以包含若干成员变量,每个成员变量的类型可以不同;可以通过结构体来定义结构体变量,每个变量拥有相同的性质。下面我们来看下结构体。

//定义结构体 Student
struct Student{
    //结构体包含的成员变量
    char *name;
    int age;
    float score;
};

上面是一个学生的结构体,里面包含这多种的数据类型,C++中的类就是和这个结构体是类似的,它也是可以包含多种的数据类型,同时它也进行了一些扩展,类的成员不但可以是变量,还可以是函数;通过类定义出来的变量也有特定的称呼,叫做“对象”。当然结构体中也是可以加入函数,不过结构体中加入的是函数指针,也就是函数的入口地址,有兴趣的可以查下。

//通过class关键字类定义类
class Student{
public:
    //类包含的变量
    char *name;
    int age;
    float score;
    //类包含的函数
    void display (){
        printf("%s的年龄是 %d,成绩是 %f\n", name, age, score);
    }
};

这个上面便是一个类的定义,有人会觉得这段代码并不规范,请忽略这一细节,本节的重点是引入类和对象的概念。这套 C++ 教程是在C的基础上编写的,我并不希望一开始就提出过多的 C++ 概念,让读者吃不消,我希望读者从C逐渐过渡到 C++。class 和 public 都是 C++ 中的关键字,初学者请先忽略 public(后续会深入讲解),把注意力集中在 class 上,这是创建一个类的关键词。在C++中,我们可以包含像上面display的函数。
结构体和类都可以看做一种由用户自己定义的复杂数据类型,在C语言中可以通过结构体名来定义变量,在 C++ 中可以通过类名来定义变量。不同的是,通过结构体定义出来的变量还是叫变量,而通过类定义出来的变量有了新的名称,叫做对象(Object)。
那么如何创建一个对象呢?其实这个和创建变量是一样的,前面是类名,然后跟着是对象名称:Student stu1;----这样子我们就创建了一个叫stu1的实例对象。
如何使用这个类中的变量和函数呢?如果是普通对象,那么我们直接使用点(.)就好了,当然如果是指针对象那么我们就要使用箭头(->)
说了这么多,相信大家对于这个类和对象有一定的理解了吧。类就是我们通过class定义的一个自定义数据类型,而对象就是我们利用自定义的数据类型创建的一个变量,当然这个变量也是叫做类的实例化。

二、 什么是面向对象编程

面向对象编程——Object Oriented Programming,简称OOP,是一种程序设计思想。OOP把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数。
面向过程的程序设计把计算机程序视为一系列的命令集合,即一组函数的顺序执行。为了简化程序设计,面向过程把函数继续切分为子函数,即把大块函数通过切割成小块函数来降低系统的复杂度。
而面向对象的程序设计把计算机程序视为一组对象的集合,而每个对象都可以接收其他对象发过来的消息,并处理这些消息,计算机程序的执行就是一系列消息在各个对象之间传递。
上面说的有拗口,那么下面就用一些通俗易懂的话来说。比如说我们做菜来说,做一个酸菜鱼,用面向对象来做鱼的话,我们就把那些步骤变成一个个对象,比如:洗鱼、煮鱼、放调料等等,每一个就是一个对象,我们需要执行对应对象的操作,就调用对应的对象,所有的数据都是在每个对象之间传递。
而相对的面向过程的话,就是把上面每个对象执行的步骤都用一个个函数写出来,没有对象的概念,简单点就是把需要做的事情都一步步写出来,重点就是过程二字。

三、 面向对象的特性

三大基本特性:封装,继承,多态
封装,就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。一个类就是一个封装了数据以及操作这些数据的代码的逻辑实体。在一个对象内部,某些代码或某些数据可以是私有的,不能被外界访问。通过这种方式,对象对内部数据提供了不同级别的保护,以防止程序中无关的部分意外的改变或错误的使用了对象的私有部分。
继承,指可以让某个类型的对象获得另一个类型的对象的属性的方法。它支持按级分类的概念。继承是指这样一种能力:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。 通过继承创建的新类称为“子类”或“派生类”,被继承的类称为“基类”、“父类”或“超类”。继承的过程,就是从一般到特殊的过程。要实现继承,可以通过 “继承”(Inheritance)和“组合”(Composition)来实现。继承概念的实现方式有二类:实现继承与接口继承。实现继承是指直接使用父类的属性和方法而无需额外编码的能力;接口继承是指仅使用属性和方法的名称、但是子类必须提供实现的能力。
多态,是指一个类实例的相同方法在不同情形有不同表现形式。多态机制使具有不同内部结构的对象可以共享相同的外部接口。这意味着,虽然针对不同对象的具体操作不同,但通过一个公共的类,它们(那些操作)可以通过相同的方式予以调用。

四、 命名空间

为了解决变量命名的问题,C++引入了命名空间的概念,从字面的意思上理解,这个命名空间就是一段独立的空间,你的命名只要在对应的空间上才会有对应的含义,否则就没有这样的含义。当然每段的命名空间是独立的,不然怎么解决这个命名重复的问题。

namespace Li{  //小李的变量定义
    FILE fp = NULL;
}
namespace Han{  //小韩的变量定义
    FILE fp = NULL
}

小李与小韩各自定义了以自己姓氏为名的命名空间,此时再将他们的 fp 变量放在一起编译就不会有任何问题。相同的这两个变量也是相互隔离开的,如果你要使用小李的变量那么就需要加上Li::(Li::fp)
namespace 是C++中的关键字,用来定义一个命名空间。后面加上名称和大括号,就可以创建一个命名空间。使用的时候需要加上域操作符(::),除了直接使用域解析操作符,还需要采用 using 关键字声明。

using Li::fp;
fp = fopen("one.txt", "r");  //使用小李定义的变量 fp
Han :: fp = fopen("two.txt", "rb+");  //使用小韩定义的变量 fp

当然我们C++也有一个很好用的命名空间std,这个命名空间里面定义了很多变量,像是cout,cin,map,list这些都是在这里面。

五、 布尔类型(bool)

在C语言中,关系运算和逻辑运算的结果有两种,真和假:0 表示假,非 0 表示真。C语言并没有彻底从语法上支持“真”和“假”,只是用 0 和非 0 来代表。这点在 C++ 中得到了改善,C++ 新增了 bool 类型(布尔类型),它一般占用 1 个字节长度。bool 类型只有两个取值,true 和 false:true 表示“真”,false 表示“假”。 bool 是类型名字,也是 C++ 中的关键字。但是在 C++ 中使用 cout 输出 bool 变量的值时还是用数字 1 和 0 表示,而不是 true 或 false。

六、 new和delete运算符

在C语言中,动态分配内存用 malloc() 函数,释放内存用 free() 函数。

int *p = (int*) malloc( sizeof(int) * 10 );  //分配10个int型的内存空间
free(p);  //释放内存

在C++中,这两个函数仍然可以使用,但是C++又新增了两个关键字,new 和 delete:new 用来动态分配内存,delete 用来释放内存。

int *p = new int;  //分配1个int型的内存空间
delete p;  //释放内存
如果希望分配一组连续的数据,可以使用 new[]int *p = new int[10];  //分配10个int型的内存空间
delete[] p;

用 new[] 分配的内存需要用 delete[] 释放,它们是一一对应的。
和 malloc() 一样,new 也是在堆区分配内存,必须手动释放,否则只能等到程序运行结束由操作系统回收。为了避免内存泄露,通常 new 和 delete、new[] 和 delete[] 操作符应该成对出现,并且不要和C语言中 malloc()、free() 一起混用。

那么这个new和malloc有什么区别呢?下面我们来说说。

  1. new/delete是关键字,效率高于malloc和free。new和delete是C++关键字,需要编译器支持;malloc和free是库函数,需要头文件支持。
  2. 使用new操作符申请内存分配时无须指定内存块的大小,编译器会根据类型信息自行计算。而malloc则需要显式地指出所需内存的尺寸。
  3. new操作符内存分配成功时,返回的是对象类型的指针,类型严格与对象匹配,无须进行类型转换,故new是符合类型安全性的操作符。而malloc内存分配成功则是返回void
    • ,需要通过强制类型转换将void*指针转换成我们需要的类型。
  4. new会先调用operator
    new函数,申请足够的内存(通常底层使用malloc实现)。然后调用类型的构造函数,初始化成员变量,最后返回自定义类型指针。delete先调用析构函数,然后调用operator
    delete函数释放内存(通常底层使用free实现)。malloc/free是库函数,只能动态的申请和释放内存,无法强制要求其做自定义类型对象构造和析构工作。
  5. C++允许自定义operator new 和 operator delete 函数控制动态内存的分配。
  6. new操作符从自由存储区(free
    store)上为对象动态分配内存空间,而malloc函数从堆上动态分配内存。自由存储区是C++基于new操作符的一个抽象概念,凡是通过new操作符进行内存申请,该内存即为自由存储区。而堆是操作系统中的术语,是操作系统所维护的一块特殊内存,用于程序的内存动态分配,C语言使用malloc从堆上分配内存,使用free释放已分配的对应内存。自由存储区不等于堆,如上所述,布局new就可以不位于堆中。
  7. new内存分配失败时,会抛出bac_alloc异常。malloc分配内存失败时返回NULL。
  8. 内存泄漏对于new和malloc都能检测出来,而new可以指明是哪个文件的哪一行,malloc确不可以。

七、 C++中的默认参数

在C++中,定义函数时可以给形参指定一个默认的值,这样调用函数时如果没有给这个形参赋值(没有对应的实参),那么就使用这个默认的值。也就是说,调用函数时可以省略有默认值的参数。如果用户指定了参数的值,那么就使用用户指定的值,否则使用参数的默认值。所谓默认参数,指的是当函数调用中省略了实参时自动使用的一个值,这个值就是给形参指定的默认值。

//带默认参数的函数
void func(int n, float b=1.2, char c='@')
{
    cout<<n<<", "<<b<<", "<<c<<endl;
}

指定了默认参数后,调用函数时就可以省略对应的实参了。但是要记得,使用默认参数的时候,默认参数都是定义在最后的,也就是普通参数之后。下面的几种定义方式是不合法的。

void func(int a, int b=10, int c=20, int d){ }
void func(int a, int b=10, int c, int d=20){ }

八、 C++函数重载

在我们的实际开发过程中,很容易遇到同一个函数,但是需要使用不同的数据类型进行调用,面对这种情况,在C语言中我们通常会定义很多的函数,但是在C++中并不需要如此,只需要定义一种函数就行,这种函数我们可以使用重载的方式,就不需要写那么多了函数名称,统一使用一个名称就行。
所谓的函数重载就是:一个函数名多种用途。下面就有几个函数重载的例子。

//交换 int 变量的值
void Swap(int *a, int *b){
    int temp = *a;
    *a = *b;
    *b = temp;
}
//交换 float 变量的值
void Swap(float *a, float *b){
    float temp = *a;
    *a = *b;
    *b = temp;
}

在使用重载函数时,同名函数的功能应当相同或相近,不要用同一函数名去实现完全不相干的功能,虽然程序也能运行,但可读性不好,使人觉得莫名其妙。

那么重载的规则有哪些呢?

  1. 函数名称必须相同。
  2. 参数列表必须不同(个数不同、类型不同、参数排列顺序不同等)。
  3. 函数的返回类型可以相同也可以不相同。
  4. 仅仅返回类型不同不足以成为函数的重载。

C++是如何做到重载的呢?
原来C++代码在编译时会根据参数列表对函数进行重命名,例如void Swap(int a, int b)会被重命名为_Swap_int_int,void Swap(float x, float y)会被重命名为_Swap_float_float。当发生函数调用时,编译器会根据传入的实参去逐个匹配,以选择对应的函数,如果匹配失败,编译器就会报错,这叫做重载决议(Overload Resolution)。
当然,不同的编译器有不同的重命名方式,这里仅仅举例说明,实际情况可能并非如此。从这个角度讲,函数重载仅仅是语法层面的,本质上它们还是不同的函数,占用不同的内存,入口地址也不一样。

九、 C++的引用

引用变量是一个别名,也就是说,它是某个已存在变量的另一个名字。一旦把引用初始化为某个变量,就可以使用该引用名称或变量名称来指向变量。也就是说,使用引用的话,两个变量名是指向同一块内存的,所以他们两个保存是同一个数据,一边发生改变另一边也会发生改变。

int&  r = i;
double& s = d;

上面的例子便是创建一个引用,在这些声明中,& 读作引用。因此,第一个声明可以读作 “r 是一个初始化为 i 的整型引用”,第二个声明可以读作 “s 是一个初始化为 d 的 double 型引用”。
引用通常用于函数参数列表和函数返回值。

//作为函数参数的例子
int Swap(int& a, int& b) {}
//作为函数返回值的例子
double vals[] = {10.1, 12.6, 33.1, 24.1, 50.0};

double& setValues(int i) {  
   double& ref = vals[i];    
   return ref;   // 返回第 i 个元素的引用,ref 是一个引用变量,ref 引用 vals[i],最后再返回 shit。
}

那么引用和我们指针有啥区别呢?

  1. 两者的定义和性质不同。指针是一个变量,存储的是一个地址,指向内存的一个存储单元;引用是原变量的一个别名,跟原来的变量实质上是同一个东西。
    指针与引用
  2. 指针可以有多级,引用只能是一级。
  3. 指针可以在定义的时候不初始化,引用必须在定义的时候初始化
  4. 指针可以指向NULL,引用不可以为NULL
  5. sizeof 的运算结果不同
  6. 自增运算意义不同;指针是指内存地址增加,而引用是指数据增加
  7. 指针和引用作为函数参数时,指针需要检查是否为空,引用不需要
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值