第一章 初识C++

1.1 C++概述

不同于C语言, C++是一种面向对象的语言,在C语言的基础上,C++扩充了一些自己特有的知识,如bool类型、重载函数、模板、STL等。
C语言:面向过程的,注重过程的实现,而C++:面向对象。
举个小例子:我吃饭。
用C语言来实现:吃(人类 变量,食物类 变量)。
用C++语言来实现:我.吃(饭)
一切都是这么自然,符合自然规律。此外,面向对象程序设计方法还有三大特征:封装、继承和多态。封装:举个自动洗衣机的实例。继承:继承父辈的一些特征。多态:不同对象收到同一个消息表现不同行为。例如:两个同学面前有西瓜和苹果。收到消息:吃。他们两个的行为是不同的。或者说:下课。不同同学表现的行为是不同的。(整本书均围绕这三大特征展开的)

1.2 C++的程序框架与C的不同

#include<stdio.h>
int main()
{
   printf(“你们好,17计科的童鞋们,我是吕聪颖\n”);
   return 0;
}

而C++:

#include <iostream>
using namespace std;
int main()
{
	cout << "你们好,17计科的童鞋们,我是吕聪颖" << endl;
	return 0;
}

第一点:头文件iostream,标准的C++头文件。
旧的标准C++中,使用的是iostream.h,实际上这两个文件是不同的,采用iostream.h导致系统速度慢,被业界耻笑,现在C++标准明确提出不支持后缀为.h的头文件。还有诸如#include,#include等等,不只是形式上的改变,其实现也有所不同。
第二点:using namespace std
(1)namespace是命名空间的关键字,在c++中,name可以是符号常量、变量、函数、类、对象、cout、endl等等。命名空间实际上是由程序设计者命名的内存区域,他们可以把一些自己定义的变量、函数等标识符存放在这个空间中,与其他实体定义分割开来。命名空间的作用就是建立一些互相分割的作用域,例如三个名字相同的同学,如果分在一个班级里,则点名时会出现不确定性,如果分在不同的班级则点名时就没有歧义了。
(2)自己可以定义命名空间:

namespace A
{
  int  i=10;
}
namespace B
{
   int  i=10;
   namespace C  //命名空间的定义可以嵌套
   {
       int i=30;
   }
}
#include <iostream>
using namespace std;
int main()
{
	cout<<A::i<<endl;   或者using namespace A;
   cout<<B::i<<endl;   
	return 0;
}

问题1:使用时需要指明是哪个命名空间的标识符,如A::i
问题2:使用using namespace A; 可以直接使用i
问题3:命名空间的定义能否嵌套?
问题4:C语言的缺点:要开发一个大型的信息系统,需要调用多个动态库中的函数,假如库1和库2均有一个数据加密函数,二者名称相同但实现不同,在C中可能会出现调用错误的情况。而若采用C++的命名空间,在调用时指明是哪个空间的函数,则可避免出错。
(3)C++标准库中的所有标识符都被定义于一个名为std的namespace中,所以std又称作标准命名空间,要使用其中定义的标识符就要引入std空间。

#include <iostream>
using namespace std;
int main()
{
	cout << "你们好,16计科的童鞋们,我的名字是吕聪颖" << endl;
	return 0;
}

问题1:去掉using namespace std; 这条语句会怎样?编译器抱怨。怎么解决?std::cout,std::endl。std是全局命名空间。
(4)cin和cout
在C++中可以使用scanf和printf,但它们是c语言的标准输入输出函数。在C++中输入输出都是以“流”的形式实现的,C++定义了iostream流类库,包含两个基础类istream和ostream,分别定义了标准输入流对象cin来处理输入,标准输出流对象cout来处理输出。

1.3 C++对C语言的扩充

1.3.1 bool类型(取值true 和false)

【例1】

bool  i, j,k;
i=10;
j=4>3;
k=true;
cout<<i<<”  ”<<j<<”  ”<<k<<endl;

1.3.2 C++中的类型转换

C有隐式转换和强制转换,比较简单。但并不能完全满足C++的使用要求,所以C++提供了自己的类型转换操作符,不管你喜不喜欢,但得保证程序运行正确。
(1) static_cast<> 静态类型转换操作符,最常用的一种,编译器编译时会做类型检查。

          double dpi=3.14;
          int num1=(int)dpi; //C类型转换方式
          int num2=static_cast<int>(dpi); //C++类型转换方式

C语言中隐式类型转换的地方均可用static_cast<>()进行类型转换。
问题1: char*==>int *

        char*p1=”hello”;
        int*p2=NULL;
        p2=static_cast<int*>(p1); //编译器会报错
    此时,需要用到reinterpret_cast<>

(2)reinterpret_cast<> 强制解释类型转换操作符,有点强制转换的味道 char*==>int *

    char*p1=”hello”;
    int*p2=NULL;
    p2=reinterpret_cast<int*>(p1);

(3)const_cast<>
出现了const,什么是const? 是一个常量限定符,其作用类似于#define,但它消除了#define的不安全性,因此C++建议用const取代#define来定义常量。
【例】#define的不安全性

#include<iostream>
using namespace std;
int main()
{
   int a=1;
   #define T1 a+a     //简单的原样替换
   #define T2 T1-T1
   cout<<"T2 is "<<T2<<endl;
   return 0;
}


     【例】用const 取代#define
         #include<iostream>
         using namespace std;
int main()
{
   int a=1;
   const T1=a+a;   //等价于const int T1=a+a,如果const定义的是整型常量,可以省略int,常量一旦被建立,在程序的任何地方都不能被更改。
   const T2=T1-T1;
   cout<<"T2 is "<<T2<<endl;
   return 0;
}

【深入内容】const与指针的结合
【结合1】指向常量的指针:const char* pc=“hello”; 该语句定义了一个名为pc的指针变量,它指向一个字符串hello。由于使用了const,不允许改变指针所指的常量,如果pc[2]=’x’是错误的。pc是一个指向常量的指针变量,因此可以改变pc的指向,pc=”abcd”是正确的。
【结合2】常指针:char* const pc=”hello”;该语句定义了一个名为pc的指针变量,可以改变它所指向的数据,但不能改变pc的指向,也即不能改变pc的值。如果pc[2]=’x’是正确的,但pc=”abcd”是错误的。
【结合3】指向常量的常指针:const char* const pc=”hello”;不能改变pc的值也不能改变pc所指向空间的数据。
【函数参数用const说明,用于保证实参在函数内部不被改动。例如,通过函数imax求数组a[200]中的最大值,函数原型应该是:
int imax(const int* pc);确保原数组的数据只能在函数内读不允许写。(举个例子说明)
问题:如果函数原型int imax(const int* pc);这样定义了,还想在函数内部修改原数组内容,怎么办?去掉const属性即可。

int imax(const int* pc)
{
  int*p1=NULL;
  p1=const_cast<int*>(pc);
  p1[20]=100;
   ......
}

程序员必须要确保内存空间确实能修改,否则会带来灾难性的后果。

#include <iostream>
using namespace std;
void printbuf(const char*p)
{
	char* p1=NULL;
	p1=const_cast<char*>(p);
	p1[3]='c';
}
int main()
{
	  char* pc="hello";  //char pc[]=”hello”; 换成数组就可以。
     printbuf(pc);
	  cout<<pc<<endl;
	return 0;
}

(4)dynamic_cast<> 动态转换操作符,只在多态类型时合法。随后用到再讲。

1.3.3 C++中的字符串——string

C语言中处理字符串是通过字符数组来实现的,C++也支持这种风格,它还提供了一种自定义数据类型——string,是C++标准模板库STL中的一个字符串类,包含在头文件string中。用它来定义字符串,不必担心字符串长度,越界,内存不足等问题,string类全权负责一切事宜。
(1)用string定义字符串

 #include<iostream>
#include<string>
using namespace std;
int main()
{
	  string s1;
	  s1="My dear students,";
    string s2="let's start learning ";
	  string s3("C++,");
	  string s4(6,'h');     //四种方式,其实调用了string的构造函数、拷贝构造函数和带参数的构造函数
	  cout<<s1+s2+s3+s4<<endl;
	 return 0;
}

(2)通过函数实现(初始化,遍历,连接,查找,替换,区间删除,反转等操作)

1.3.4 引用

引用是隐式的指针。什么是引用呢?通俗来说:起别名。
(1)引用的定义
【例】

int a=10;
int &b=a; //必须用相同数据类型的变量初始化。定义时就需要初始化。
b=20;

【例子】int &c=10;//错误的,引用在初始化时只能绑定左值,不能绑定常量值
【例】

int a=10;
int &b=a;
int j=30;
b=j;  //会发生什么情况?

(2)C++增加引用的重要作用:作为函数参数,比指针做形参更方便

#include <iostream>
using namespace std;
void swap(int& x, int& y)
{
	int temp = x;
	x = y;
	y = temp;
}
int main()
{
	int a, b;
	cout << "please input two nums: " << endl;
	cin >> a >> b;
	swap(a, b);
	cout << "swap: " << a << " " << b << endl;
	system("pause");
	return 0;
}

使用引用就是直接操作变量,简单高效可读性又好。

1.3.5 动态分配内存(new 与delete)

C中使用malloc函数和free函数来进行动态内存的分配和释放。但对于struct、enum和class等数据类型,这两个函数无法满足动态对象的需求,因此C++引入了new与delete来进行内存申请和释放。
例子1:

#include <iostream>
using namespace std;
int main()
{
    int i;
    cin>>i;
int *pc = new int[i]; //new 一个新的字符数组,含i个元素,没有提供初始值
	for (int j = 0; j < i; j++)
		pc[j] = 1 ;    //向数组中存入元素
	for (int j = 0; j < i; j++)
		cout << pc[j] << " ";
	cout << endl;
	delete[]pc;    //释放数组对象
	system("pause");
	return 0;
}

例子2:

#include <iostream>
using namespace std;
int main()
{
  int *pi = new int(10); //动态分配一个存放int型数据的内存空间,初始值为10,将首地址赋给pi ;如果int *pi=new int;则没有提供初始值
	cout << "*pi = " << *pi << endl;
	*pi = 20;    //通过指针改变变量的值
	cout << "*pi = " << *pi << endl;
	char *pc = new char[10]; //new 一个新的字符数组,含10个元素,没有提供初始值
	for (int i = 0; i < 10; i++)
		pc[i] = i + 65;    //向数组中存入元素
	for (int i = 0; i < 10; i++)
		cout << pc[i] << " ";
	cout << endl;
	delete pi;      //释放int对象
	delete[]pc;    //释放数组对象
	system("pause");
	return 0;
}

问题:new可以自动计算所要分配内存的类型大小,不必使用sizeof()来计算所需要的字节数。

struct node
{
	int i;
	struct node *next;
};
//p=(struct node*)malloc(sizeof(struct node)); 用C的malloc分配内存的方法
#include<iostream>
using namespace std;
int main()
{
   struct node *p;
   p=new struct node; //用new分配内存的方法
   p->i=10;
   p->next=NULL;
   cout<<p->i<<endl;
   return 0;
}

注意:为了保持与C的兼容,malloc和free在C++中同样可以使用。

1.3.6默认参数

默认。安装软件时,有很多选项设置了默认,可以不管它继续安装,也可以更改默认选项。C++的函数也支持默认参数机制,即在定义或声明函数时给形参一个初始值,调用函数时,如果不传递实参就使用默认参数值。
例如:

#include <iostream>
using namespace std;
void add(int x, int y = 1, int z = 2); //函数声明中有两个形参有默认值
int main()
{
	add(1);  //只传递1给形参x,而y、z使用默认形参值
	add(1, 2); //传递1给x,2给y,而z使用默认形参值
	add(1, 2, 3); //传递三个参数,不使用默认形参值
	system("pause");
	return 0;
}
void add(int x, int y, int z)
{
	cout << x + y + z << endl;
}

问题1:默认参数出现的位置?如上例,既有函数声明又有函数定义,则默认参数只能在函数声明中设定。如果没有函数声明,才可以在函数定义中设定。
问题2:void add(int x, int y = 1, int z ); 错误,默认参数后面不能再有普通的形参,因为默认参数定义的顺序是自右向左。

1.3.7内联函数

函数的调用有利于代码重用,提高效率,但有时频繁的函数调用也会增加时间与空间的开销反而造成效率低下。因为调用函数实际上是将程序执行顺序从函数调用处跳转到函数所存放在内存中的某个地址,将调用现场保留,跳转到那个地址将函数执行,执行完毕后再回到调用现场,所以频繁的函数调用会带来很大开销。
为了解决该问题,C++提供了内联函数,编译时将函数体嵌入到调用处。
【例子】

#include <iostream>
using namespace std;
inline void func() //内联函数
{
	cout << "这是一个内联函数" << endl;
}
int main()
{
	func(); //内联函数调用
	system("pause");
	return 0;
}

当调用内联函数时,编译器就会把该函数体代码插入到调用位置,如下所示:

int main()
{
	cout << "这是一个内联函数" << endl;
	system("pause");
	return 0;
}

问题1:虽然节省了开销,但可能会造成代码膨胀,因此一般将结构简单语句少的函数定义为内联函数,不可以包含复杂的控制结构,递归函数是不可以定义成内联函数的。
问题2:inline只是建议编译器将函数嵌入到调用处,编译器会根据函数的长度、复杂度自行决定是否把函数作为内联函数来调用。由此可见,内联函数是对编译器的一种请求。

1.3.8重载函数

举个例子:一个班级两个同学的姓名相同(作用域相同姓名相同),但是身高、体重、性别和外貌等会有所不同,老师点名时会根据他们的特征来区分,如高个某某,胖某某。在编程语言里也会出现这种情况,几个实现不同功能的函数有着相同的函数名,在调用时需要根据参数的不同来确定调用哪个函数,这就是C++提供的函数重载机制。
所谓重载函数就是在同一个作用域内几个函数名字相同但形参列表不同。参数列表不同有三种含义:参数个数不同,或参数类型不同或者参数个数和类型都不同。
【例】重载函数实例

#include <iostream>
using namespace std;
void add(int x, int y)
{
	cout << "int: " << x + y << endl;
}
void add(float x)
{
	cout << "float: " << 10 + x << endl;
}
double add(double x, double y)
{
	return x + y;
}
int main()
{
	add(10.2);  //一个实型参数
	add(1, 3);  //两个整型参数
	cout << add(3.2, 2.3) << endl;  //两个实型参数
	system("pause");
	return 0;
}

注意:重载函数不能用返回值来区分。
(1)重载和默认参数
当重载的函数中,包含具有默认参数的函数时,必须注意防止调用的二义性。
例如:int add(int x, int y=2)
void add(int x)
如果函数调用:add(4),编译器将无法确认到底要调用哪个重载函数。
(2)重载与const形参
当重载的函数中,包含形参由const修饰的函数时。
【例子】

#include <iostream>
using namespace std;
void func1(const int *x) //常量指针
{
	cout << "const int*: " << *x << endl;
}
void func1(int *x) //普通指针
{
	cout << "int*: " << *x << endl;
}
void func2(const int &x) //常引用
{
	cout << "const int&: " << x << endl;
}
void func2(int &x) //普通引用
{
	cout << "int&: " << x << endl;
}
int main()
{
	const int a = 1;
	int b = 2;
	func1(&a); //常量参数
	func1(&b); //非常量参数

	func2(a);  //常量参数
	func2(b);  //非常量参数
	system("pause");
	return 0;
}

注意:(1)编译器可以根据实参是否是常量来推断应该调用哪个函数。const对象只能传递给const形参。
(2)非const对象可以传递给const形参,上面的重载函数都可以被非const对象调用。在重载函数中,编译器会优先选择非常量版本的函数。

猜你喜欢

转载自blog.csdn.net/lvcongying0601/article/details/84753119