c++一打开世界的大门

1、C++发展史1979年,贝尔实验室的本贾尼等人试图分析unix内核的时候,试图将内核模块化,于是在C语言的基础上进行扩 展,增加了类的机制,完成了一个可以运行的预处理程序,称之为C with classes(所以c++是可以兼容c的)。语言的发展就像是练功打怪升级一样,也是逐步递进,由浅入深的过程。我们先来看下C++的历史版本(只介绍本人认为需要了解的几个版本):

(1)c with classes:在C的基础上新增了类及派生类、公有和私有成员、类的构造和析构、友元、内联函数、赋值运算符重载等。

(2)c++98:C++标准第一个版本,绝大多数编译器都支持,得到了国际标准化组织(ISO)和美国标准化协会认可,以模板方式重写C++标准库,引入了STL(标准模板库)。

(3)c++03:C++标准第二个版本,语言特性无大改变,主要:修订错误、减少多异性。

(4)c++11:增加了许多特性,使得C++更像一种新语言,比如:正则表达式、基于范围for循环、auto关键字、新容器、列表初始化、标准线程库等。

(5)c++14:对C++11的扩展,主要是修复C++11中漏洞以及改进,比如:泛型的lambda表达式,auto的返回 值类型推导,二进制字面常量等。

2、C++关键字(c++98)关键字(keyword)又称保留字,是整个语言范围内预先保留的标识符。每个C++关键字都有特殊的含义。经过预处理后,关键字从预处理记号(preprocessing-token)中区别出来,剩下的标识符作为记号(token),用于声明对象、函数、类型、命名空间等。不能声明与关键字同名的标识符。

3、命名空间:在C/C++中,变量、函数和类都是大量存在的,这些变量、函数和类的名称将都存在于全局作用域中,可能会导致很多冲突。使用命名空间的目的是对标识符的名称进行本地化,以避免命名冲突或名字污染,namespace关键字的出现就是针对这种问题的。

(1)命名空间的定义:定义命名空间,需要使用到namespace关键字,后面跟命名空间的名字,然后接一对{}即可,{}中即为命名空间的成员。

//1. 普通的命名空间
namespace N1  // N1为命名空间的名称 
{   
    // 命名空间中的内容,既可以定义变量,也可以定义函数
    int a;   
    int Add(int left, int right)
    { 
        return left + right;
    } 
}
 
//2. 命名空间可以嵌套 
namespace N2 
{   
    int a;   
    int b;   
    int Add(int left, int right)   
    {       
        return left + right;   
    }
    namespace N3   //N3定义在N2里
    {       
        int c;       
        int d;       
        int Sub(int left, int right)       
        {           
            return left - right;       
        }   
    } 
}
 
//3. 同一个工程中允许存在多个相同名称的命名空间 
//   N1与上面定义的N1重名,进行编译后会合成为同一个命名空间中(内容扩展了)。 
namespace N1 
{   
    int Mul(int left, int right)   
    {       
        return left * right;   
    } 
}

注意:一个命名空间就定义了一个新的作用域,命名空间中的所有内容都局限于该命名空间中

(2)命名空间的使用:我们先定义一个简单的命名空间

namespace N 
{    
    int a = 10;    
    int b = 20;    
    int Add(int left, int right)    
    {        
        return left + right;    
    }
    int Sub(int left, int right)    
    {       
        return left - right;    
    } 
}
 

我们先来看一段简单代码:

//只是一段伪代码
int main(void)
{
    printf("a=%d\n",a);
    return 0;
}

当我们直接使用命名空间里定义的变量a时,编译就会报错,无法识别a。所以接下来接受命名空间使用的三种方式:

●加命名空间名称及作用域限定符

int main(void) 
{    
    printf("%d\n", N::a);    
    return 0;    
}

●使用using将命名空间中成员引入

using N::b;
 
int main() 
{    
    printf("%d\n", N::a);    
    printf("%d\n", b);    
    return 0;    
}

●使用using namespace 命名空间名称引入

using namespce N;
 
int main() 
{    
    printf("%d\n", N::a);    
    printf("%d\n", b);    
    Add(10, 20);
    Sub(20, 10);
    return 0;    
} 

4、C++的输入&输出:使用cin和cout作为输入输出。

(1)使用cout输出和cin输入时,必须包含< iostream >头文件 
注意:后缀为.h的头文件 C++标准已经不支持了,早期的实现将标准库功能定义在全局域中,声明在.h后缀的头文 件中。C++为了和C区分,也为了正确使用命名空间,规定头文件不使用.h

(2)cout和cin包含在std标准命名空间中,使用时必须包含该命名空间                                                                                        注意:旧编译器(vc6.0)中可能含有<iostream.h>头文件,此时可以不用添加std标准命名空间。 推荐使用<iostream>+std的方式。

(3)C++中使用cout进行输出(到控制台),使用cin进行输入(来源自控制台),C的方式可继续使用

(4)使用C++输入输出更方便,不需增加数据格式控制,比如:整形--%d,字符--%c

#include <iostream> using namespace std;
 
int main() 
{    
    int a;    
    double b;    
    char c;        
    cin>>a;    
    cin>>b>>c;        
    cout<<a<<endl;    
    cout<<b<<"  "<<c<<endl;        
    return 0; 
} 

5、缺省参数:缺省参数是声明或定义函数时为函数的参数指定一个默认值。在调用该函数时,如果没有指定实参则采用该默认 值,否则使用指定的实参。

void TestFunc(int a = 0) 
{    
    cout<<a<<endl; 
}
 
int main() 
{    
    TestFunc();     // 没有传参时,默认使用缺省参数    
    TestFunc(10);   // 传参时,使用指定的实参 
} 

缺省参数可以分为:全缺省参数和半缺省参数(也叫局部缺省)

(1)全缺省参数:申明或定义函数时给所有的参数都指定默认值。

void TestFunc(int a = 10, int b = 20, int c = 30) 
{    
    cout<<"a = "<<a<<endl;    
    cout<<"b = "<<b<<endl;    
    cout<<"c = "<<c<<endl; 
}

(2)半缺省参数:申明或定义函数时指定部分参数的默认值。

void TestFunc(int a, int b = 10, int c = 20) 
{    
    cout<<"a = "<<a<<endl;    
    cout<<"b = "<<b<<endl;    
    cout<<"c = "<<c<<endl; 
}

注意:1. 半缺省参数必须从右往左依次来提供,不能间隔着给出       2. 缺省参数不能同时在函数声明和定义中出现        3.缺省值必须是常量或者全局变量        4. C语言不支持(编译器不支持) 

 ●当声明与定义分离时,只能在声明中出现。在定义中会出现编译错误。

//pub.h 
int Add(int a , int b = 10);
//pub.cpp 
int Add(int a , int b) 
{    
    return a+b; 
}

6、函数重载:是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数、类型、顺序)必须不同,常用来处理实现功能类似数据类型不同的问题。

int Add(int left, int right) 
{    
    return left+right; 
}
 
double Add(double left, double right) 
{   
    return left+right; 
}
 
long Add(long left, long right) 
{    
    return left+right; 
}
 
int main() 
{    
    Add(10, 20);    
    Add(10.0, 20.0);    
    Add(10L, 20L);        
    return 0; 
} 

(1) 名字修饰(name Mangling) 
我们知道,在C/C++中,一个程序要运行起来,需要经历以下几个阶段:预处理、编译、汇编、链接。Name Mangling是一种在编译过程中,将函数、变量的名称重新改编的机制,简单来说就是编译器为了区分各个 函数,将函数通过一定算法,重新修饰为一个全局唯一的名称。

●C语言的名字修饰规则非常简单,只是在函数名字前面添加了下划线。比如,对于以下代码,在后链接时就会出 错:

int Add(int left, int right);
 
int main() 
{    
    Add(1, 2);    
    return 0; 
}

(编译器报错:error LNK2019: 无法解析的外部符号 _Add,该符号在函数 _main 中被引用。)

上述Add函数只给了声明没有给定义,因此在链接时就会报错,提示:在main函数中引用的Add函数找不到函数体。从报错结果中可以看到,C语言只是在函数名前添加了下划线。因此当工程中存在相同函数名的函数,由于没有做过多的区分就会产生冲突。

●由于C++要支持函数重载,命名空间等,使得其修饰规则比较复杂,不同的编译器在底下实现的方式可能都有差 异。

int Add(int left, int right);
 
double Add(double left, double right);
 
int main() 
{    
    Add(1, 2);    
    Add(1.0, 2.0);    
    return 0; 
} 

在vs下,对上述代码进行编译链接,后编译器报错: 

          error LNK2019: 无法解析的外部符号 "double cdecl Add(double,double)" (?Add@@YANNN@Z)
          error LNK2019: 无法解析的外部符号 "int __cdecl Add(int,int)" (?Add@@YAHHH@Z)

通过上述错误可以看出,编译器实际在底层使用的不是Add名字,而是被重新修饰过的一个比较复杂的名字,被重新修饰后的名字中包含了:函数的名字以及参数类型。这就是为什么函数重载中几个同名函数要求其参数列表不同的原因。只要参数列表不同,编译器在编译时通过对函数名字进行重新修饰,将参数类型包含在终的名字中,就可保证名字在底层的全局唯一性。

(2) extern “C” 有时候在C++工程中可能需要将某些函数按照C的风格来编译,在函数前加extern "C",意思是告诉编译器,将该函数按照C语言规则来编译。

extern "C" int Add(int left, int right);
 
int main() 
{    
    Add(1,2);    
    return 0; 
} 

链接时报错:error LNK2019: 无法解析的外部符号_Add,该符号在函数 _main 中被引用。

7、引用:引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。

类型& 引用变量名(对象名) = 引用实体;

void TestRef() 
{   
    int a = 10;   
    int& ra = a;    //<====定义引用类型       
    printf("%p\n", &a);   
    printf("%p\n", &ra); 
}

注意:引用类型必须和引用实体是同种类型的

(1)引用特性:

●引用在定义时必须初始化

●一个变量可以有多个引用

●引用一旦引用一个实体,再不能引用其他实

void TestRef() 
{   
    int a = 10;   // 
    int& ra;   // 该条语句编译时会出错   
    int& ra = a;   
    int& rra = a;   
    printf("%p  %p  %p\n", &a, &ra, &rra);  //打印时,这三个地址相同
}

(2)常引用:

void TestConstRef() 
{   
    const int a = 10;   
    //int& ra = a;   // 该语句编译时会出错,a为常量   
    const int& ra = a;

    const int b = 10;   
    // int& b = 10;  // 该语句编译时会出错,b为常量   
    const int& b = 10;
   
    double d = 12.34;   
    //int& rd = d;  // 该语句编译时会出错,类型不同   
    const int& rd = d; //可以通过,相当于取d的值,再做Int强转。实际应用中不建议
}

(3)使用场景:

●作形参

void Swap(int& left, int& right) 
{   
    int temp = left;   
    left = right;   
    right = temp; 
}

●作返回值

int& TestRefReturn(int& a) 
{   
    a += 10;   
    return a; 
}

注意:不能返回栈空间上的引用

(4) 传值、传地址、传引用效率比较 :

#include<iostream>
#include<winbase.h>
using namespace std;

typedef struct _A{    //定义一个结构用于测试
	
	int buf[1000000];
}A;

void Test(A& a){
	a.buf[0] = 0;
	a.buf[1] = 1;
}

void TestPtr(A *a){
	a->buf[0] = 0;
	a->buf[1] = 1;
}

int main(void){
	int i = 0;
	//传引用
	long begin = GetTickCount();	//记录执行代码前的时间
	A a;
	for (;i < 1000000;i++){
		Test(a);
	}
	long end = GetTickCount();	//记录执行完Test时的时间
	cout<<"传引用所需的时间"<<end - begin<<endl;	

	//传指针
	long beginPtr = GetTickCount();	//记录执行代码前的时间
	A *b;
	for (;i < 1000000;i++){
		TestPtr(b);
	}
	long endPtr = GetTickCount();	//记录执行完TestPtr时的时间
	cout<<"传指针所需的时间"<<endPtr - beginPtr<<endl;	

	return 0;
}

通过上述代码的比较,发现引用和指针在传参上效率几乎相同。

(5) 引用和指针的区别:

在语法概念上引用就是一个别名,没有独立空间,和其引用实体共用同一块空间。

我们来看下引用和指针的汇编代码对比:


我们可以看出,在底层实现上引用实际是有空间的,因为引用是按照指针方式来实现的。

引用和指针的不同点:

a. 引用在定义时必须初始化,指针没有要求

b. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体

c. 没有NULL引用,但有NULL指针

d. 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节)

e. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小

f. 有多级指针,但是没有多级引用

g. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理

h. 引用比指针使用起来相对更安全

8、内联函数:以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数压栈的开销,内联 函数提升程序运行的效率。

(1)特性:

● inline是一种以空间换时间的做法,省去调用函数额开销。所以代码很长或者有循环/递归的函数不适宜使用 作为内联函数。

● inline对于编译器而言只是一个建议,编译器会自动优化,如果定义为inline的函数体内有循环/递归等等,编 译器优化时会忽略掉内联。

(2)宏的优缺点:

优点:

●增强代码的复用性。                                                                                                                                                                        ●提高性能 

缺点:  

●不方便调试宏。(因为预编译阶段进行了替换)
●导致代码可读性差,可维护性差,容易误用。                                                                                                                               ●没有类型安全的检

 (3)c++可以替代宏的技术:

●常量定义-----换用const

●函数定义-----换用内联函数

●类型重定义-----换用 typedef 

注意: 如果成员函数不在类体内定义,而在类体外定义,系统并不把它默认为内置(inline )函数,调用这些成员函数的过程和调用一般函数的过程是相同的。如果想将这些成员函数指定为内置函数,应当用inline作显式声明。在函数的声明或函数的定义两者之一作inline声明即可。

猜你喜欢

转载自blog.csdn.net/qq_41896511/article/details/81488838