C++ 基础入门(一)简介

C++ 基础入门(一)简介

本系列主要用于温习 C++ 基础知识,查漏补缺。

C++ 简介

C++, 全称“C Plus Plus"。顾名思义,C++ 是在C语言的基础上增加新特性,所以叫"C Plus Plus"。C++ 支持面向过程编程、面向对象编程和泛型编程。

1.1 类(Class)和对象(Object)

要学习 C++,首先要理解**类(Class)对象(Object)**这两个概念。

C++ 中的类(Class)可以看作C语言中结构体(Sturct)的升级版。结构体是一种构造类型,可以包含若干成员变量,每个成员变量的类型可以不同;可以通过结构体来定义结构体变量,每个变量拥有相同的性质。例如:

1. // 定义结构体
2. struct Student{
3. 	// 结构体包含的成员变量
4. 	char *name;
5. 	int age;
6. 	float score;
7. };

C++ 中的类也是一种构造类型,但是进行了一些扩展,类的成员不但可以是变量,还可以是函数;通过类定义出来的变量也有特定的称呼,叫做”对象“。例如:

1. // 通过class关键字类定义类
2. class Student{
3. public:
4. 	// 类包含的变量
5. 	char *namel
6. 	int age;
7. 	float score;
8. 
9. 	// 类包含的函数
10.	void say(){
11.			printf("%s的年龄是 %d,成绩是 %f\n", name, age, score);
12.		} 
13. };
14. 
15. int main(){
16. 		// 通过类来定义变量,即创建对象
17. 		class Student stu;// 也可省略关键字class
18. 		...
19. 		return 0;
20. }

C语言中的 struct 只能包含变量,而 C++ 中的 class 除了可以包含变量,还可以包含函数

可以将类比喻成图纸,对象比喻成零件。类只是一张图纸,起到说明作用,不占用内存空间;对象才是具体的零件,要有地方来存放,占用内存空间。

在 C++ 中,通过类名九可以创建对象,这个过程叫做类的实例化,因此也称对象是类的一个实例(Instance)

有些资料也将类的成员变量称为属性(Property),将类的成员函数称为方法(Method)

1.2 面向对象编程(Object Oriented Programming OOP)

在 C++ 中,多了一层封装,就是类(Class)。类由一组相关联的函数、变量组成,你可以将一个类或多个类放在一个源文件,使用时引入对应的类就可以。

面向对象编程在代码执行效率上绝对没有任何优势,它的主要目的是方便程序员组织和管理代码,快速梳理编程思路,带来编程思想上的革新。

1.3 编译和运行

1.4 命名空间

为了解决合作开发时的命名冲突问题, C++ 引入了**命名空间(Namespace)**的概念。例如:

1. namespace Li{	// 小李的变量定义
2. 		FILE fp = NULL;
3. }
4. 
5. namespace Han{		// 小韩的变量定义
6. 		FILE fp = NULL;
7. }

小李与小韩各自定义了以自己姓氏为名的命名空间,此时再将他们的 fp 变量放在一起编译就不会有任何问题。

命名空间有时也被称为名字空间、名称空间。
1. namespace name{
2. 	// variables, functions, classes
3. }

name 是命名空间的名字,它里面可以包含变量、函数、类、typedef、#define 等,最后由 {} 包围。

使用变量、函数时要指明它们所在的命名空间。以上面的 fp 变量为例,可以这样来使用:

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

:: 是一个符号,称为域解析操作符,在 C++ 中用来指明要使用的命名空间。

除了直接使用域解析操作符,还可以采用using关键字声明,例如:

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

在代码的开头用 using 声明了 Li::fp,它的意思是,using 声明以后的程序中如果出现了未指定命名空间的 fp,就使用 Li:fp;但是若要使用小韩定义的 fp,仍需要 Han::fp。

using 声明不仅可以针对命名空间中的一个变量,也可以用于声明整个命名空间,例如:

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

如果命名空间 Li 中还定义了其他的变量,那么同样具有 fp 变量的效果。在 using 声明后,如果由未具体指定命名空间的变量产生了命名冲突,那么默认采用命名空间 Li 中的变量。

命名空间内部不仅可以声明或定义变量,对于其他能在命名空间以外声明或定义的名称,同样都能在命名空间内部进行声明或定义,例如类、函数、typedef、#define 等都可以出现在命名空间中。

站在编译和链接的角度,代码中出现的变量名、函数名、类名等都是一种符号(Symbol)。有的符号可以指代一个内存位置,例如变量名、函数名;有的符号仅仅是一个新的名称,例如 typedef 定义的类型别名。

1.5 C++ 头文件和 std 命名空间

C++ 是在C语言的基础上开发的,早期的 C++ 还不完善,不支持命名空间,没有自己的编译器,而是将 C++ 代码翻译成C代码,再通过C编译器完成编译。这个时候的 C++ 仍在使用C语言的库,stdio.h、stdlib.h、string.h 等头文件依然有效;此外 C++ 也开发了一些新的库,增加了自己的头文件,例如:

  • iostream.h:用于控制台输入输出的头文件
  • fstream.h: 用于文件操作的头文件
  • complex.h:用于复数计算的头文件

和C语言一样,C++ 头文件仍然以 .h 为后缀,它们所包含的类、函数、宏等都是全局范围的。

后来 C++ 引入了命名空间的概念,计划重新编写库,将类、函数、宏等都统一纳入一个命名空间,这个命名空间的名字就是 std。std 是 standard 的缩写,意思是”标准命名空间“。

但是这时已经由很多用老式 C++ 开发的程序了,它们的代码中并没有使用命名空间,直接修改原来的库会带来一个很严重的后果:程序员会因为不愿花费大量时间修改老式代码而极力反抗,拒绝使用新标准的 C++ 代码。

所以 C++ 开发人员想了一个好办法,保留原来的库和头文件,它们再 C++ 中可以继续使用,然后再把原来的库复制一份,在此基础上稍加修改,把类、函数、宏等纳入命名空间 std 下,就成了新版 C++ 标准库。这样共存在了两份功能相似的库,使用老式 C++ 的程序可以继续使用原来的库,新开发的程序可以使用新版的 C++ 库

为了避免头文件重名,新版 C++ 库也对头文件的命名做了调整,去掉了后缀 .h ,所以老式 C++ 的 iostream.h 变成了 iostream,fstream.h 变成了 fstream。而对于原来的C语言的头文件,也采用同样的方法,但在每个名字之前还要添加一个 c 字母,所以C语言的 stdio.h 变成了 cstdio,stdlib.h 变成了 cstdlib。

需要注意的是,旧的 C++ 头文件是官方所反对使用的,已明确提出不再支持,但旧的C头文件仍然可以使用,以保持对C的兼容性。实际上,编译器开发商不会停止对客户现有软件提供支持,可以预计,旧的 C++ 头文件在未来数年内还是会被支持。

对于不带 .h 的头文件,所有的符号都位于命名空间 std 中,使用时需要声明命名空间 std;对于带 .h 的头文件,没有使用任何命名空间,所有符号都位于全局作用域。这也是 C++ 标准所规定的

1.6 C++ 输入输出(cin和cout)

1.7 C++ 变量定义的位置

1.8 C++ 布尔类型(bool)

1.9 C++ new和delete运算符简介

在C语言中,动态内存分配使用 malloc() 函数,释放内存使用 free() 函数。如下所示:

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

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

用 new 和 delete 分配内存更加简单:

1. int *p = new int;// 分配1个int型的内存空间
2. delete p;// 释放内存

new 操作会根据后面的数据类型来推断所需空间的大小。

如果希望分配一组连续的数据,可以使用 new[]:

1. int *p = new int[10];// 分配10个int型的内存空间
2. delete[] p;

用 new[] 分配的内存需要用 delete[] 释放,它们是一一对应的。

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

在 C++ 中,建议使用 new 和 delete 来管理内存,它们可以使用 C++ 的一些新特性,最明显的是可以自动调用构造函数和析构函数。

1.10 内联函数

函数是一个可以重复使用的代码块,CPU 会一条一条地挨着执行其中的代码。CPU 在执行主调函数代码时如果遇到了被调函数,主调函数就会暂停,CPU转而执行被调函数的代码;被调函数执行完毕后再返回到主调函数,主调函数根据刚才的状态继续往下执行

一个 C/C++ 程序的执行过程可以认为是多个函数之间的相互调用的过程,它们形成了一个简单或复杂的调用链条,这个链条的起点是 main(),终点也是 main()。当 main() 调用完了所有的函数,它会返回一个值(例如 return 0;)来结束自己的生命,从而结束整个程序。

函数调用是有时间和空间开销的。 程序在执行一个函数之前需要做一些准备工作,需要将实参、局部变量、返回地址以及若干寄存器都压入栈中,然后才能执行函数体中的代码;函数体中的代码执行完毕后还要清理现场,将之前压入栈中的数据都出栈,才能接着执行函数调用位置以后的代码。

如果函数体代码比较多,需要较长的执行时间,那么函数调用机制占用的时间可以忽略;如果函数只有一两条语句,那么大部分的时间都会花费在函数调用机制上,这种时间开销就不容忽视。

为了消除函数调用的时间开销,C++ 提供一种提高效率的方法,即在编译时将函数调用处用函数体替换,类似于C语言中的宏展开。这种在函数调用处直接嵌入函数体的函数称为内联函数(Inline Function),又称内嵌函数或者内置函数

指定内联函数的方法很简单,只需要在函数定义处增加 inline 关键字。请看下面的例子:

1. #include <iostream>
2. using namespace std;
3.  
4. // 内联函数,交换两个数的值
5. inline void swap(int *a, int *b)
6. {
7. 		int temp;
8. 		temp = *a;
9. 		*a = *b;
10.		*b = temp; 
11. }
12.  
13. int main(){
14.  		int m,n;
15. 		cin >> m >> n;
16. 		swap(&m, &n);
17. 		cout << m < n << endl;
18.  
19. 		return 0;
20. }

运行结果:
45 99↙
45, 99
99, 45

注意,要在函数定义处添加 inline 关键字,在函数声明处添加 inline 关键字虽然没有错,但这种做法是无效的,编译器会忽略函数声明处的 inline 关键字。

当编译器遇到函数调用 swap(&m, &n) 时,会用 swap() 函数的代码替换 swap(&m, &n),同时用实参代替形参。这样,程序第16行就被置换成:

1. int temp;
2. temp = *(&m);
3. *(&m) = *(&n);
4. *(&n) = temp;

编译器可能会将*(&m)、*(&n) 分别优化成 m、n。

当函数比较复杂时,函数调用的时间开销可以忽略,大部分的 CPU 时间都会花费在执行函数体代码上,所以我们一般是将非常短小的函数声明为内联函数。

由于内联函数比较小,我们通常的做法是省略函数原型,将整个函数定义(包括函数头和函数头)放在本应该提供函数原型的地方。

内联函数的缺点也是非常明显的,编译后的程序会存在多份相同的函数拷贝,如果被声明为内联函数的函数体非常大,那么编译后的程序体积也将会变得很大,所以再次强调,一般只将那些短小的、频繁调用的函数声明为内联函数

最后需要说明的是,对函数作 inline 声明只是程序员对编译器提出的一个建议,而不是强制性的,并非一经指定为 inline 编译器就必须这样做。编译器有自己的判断能力,它会根据具体情况决定是否这样做。

1.11 C++ 函数的默认参数

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

所谓默认参数,指的是当函数调用中省略了实参时自动使用的一个值,这个值就是给形参指定的默认值。

C++ 规定,默认参数只能放在形参列表的最后,而且一旦为某个形参指定了默认值,那么它后面的所有形参都必须有默认值。 实参和形参的传值是从左到右依次匹配的,默认参数的连续性是保证正确传参的前提。

默认参数并非编程方面的重大突破,而只是提供了一种便捷的方式。在以后设计类时你将发现,通过使用默认参数,可以减少要定义的析构函数、方法以及方法重载的数量。

除了函数定义,你也可以在函数声明处指定默认参数。

C++ 函数重载详解

在实际开发中,有时候我们需要实现几个功能类似的函数,只是有些细节不同。例如希望交换两个变量的值,这两个变量有多种类型,可以是 int、float、char、bool 等,我们需要通过参数把变量的地址传入函数内部。在 C 语言中,程序员往往需要分别设计出几个不同名的函数,其函数原型与下面类似:

1. void swapInt(int *a, int *b);
2. void swapFloat(float *a, float *b);
3. void swapChar(char *a, char*b);
4. void swapBool(bool *a, bool *b);

但在 C++ 中,这完全没必要。C++ 允许多个函数拥有相同的名字,只要它们的参数列表不同就可以,这就是函数的重载(Function Overloading)。借助重载,一个函数名可以有多种用途。

参数列表又叫参数签名,包括参数的类型、参数的个数和参数的顺序,只要有一个不同就叫做参数列表不同。

【示例】***略

重载就是在一个作用范围内(同一个类、同一个命名空间等)有多个名称相同但参数列表不同的函数。重载的结果是让一个函数名拥有了多种用途,使得命名更加方便,调用更加灵活。

注意,参数列表不同包括参数的个数不同、类型不同或顺序不同,仅仅参数名称不同是不可以的。函数的返回值也不能作为重载的依据。

函数重载的规则:

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

C++ 是如何做到函数重载的

C++ 代码在编译时会根据参数列表对函数进行重命名,例如 void Swap(int a, int b) 会被重命名为 _Swap_int_intvoid Swap(float a, float b) 会被重命名为 _Swap_float_float。当发生函数调用时,编译器会根据传入的实参去主歌匹配,以选择对应的函数;如果匹配失败,编译器就会报错,这叫做重载决议(Overloading Resolution)

不同的编译器可能会有不同的重命名方式,此处仅仅举例说明,仅供参考。

从这个角度讲,函数重载仅仅是语法层面的,本质上它们还是不同的函数,占用不同的内存,入口地址也不一样。

参考链接:http://c.biancheng.net/cplus/c2cpp/

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值