您的位置:首页 > 编程语言 > C语言/C++

20161220C++阶段班02_C to C++

2016-12-20 10:46 267 查看

C过渡到C++

bool:

C++是支持原生bool的!

    在C语言中,我们使用bool类型,必须用到stdbool.h文件,他是使用宏的方式定义了true和false,这并非原始bool,是使用替代品代替的。

    C++中的bool才符合真正bool的意义,0为false,非0为true!原生bool是小写的bool,bool有很多种!宏定义都是int类型的。

        BOOL(也非原生bool),_Bool(C98标准后加上的,等同于bool),等。bool是原生的(0和非0),其他的都是通过0和1来实现的!建议一直使用bool,只有bool这是最安全的!

头文件的三种标准:

    #include <iostream>,这个库不以.h结尾,后缀名适用于区分使用,不加.h里面的内容也是一样的。
    C++是从C语言发展而来的,里面有几个标准:1:.h结尾的,就是从C继承来的。2:前面前缀c(eg:#include <cstdio>),也是从C继承过来的,但是这个符合C++标准,他和stdio.h里面基本一样,只是cstdio符合C++标准。使用C语言的标准库应该使用cstdio。3:标准C++库(#include
<iostream>)。

预编译头:

    建立文件的时候改为cpp格式,这样,软件会调用C++的编译器来编译。

    新建工程的时候,如果没点击空项目,VS默认会加上stdafx.h和stdafx.c,预编译头。他是VC所特有的,即windows下所特有的(给链接器使用)!可以没有他,以后建立工程就选空项目,预编译头是想给你提供便利,但一般是我们不需要的,也是不被C++标准承认的!

域作用符:::

  全局变量和局部变量名可以相同,不会重定义,如果在函数内调用全局变量就可以用::
eg:
int num = 100;
int main()
{
int num = 10;
int sum = num+::num;}

  第二个num就是调用的全局的,::前面没加东西就是指全局空间,加了的话就是指调用的对应命名空间里面的函数!
  printf是C语言函数,C++中是std::cout<<num;(后面没有endl也可以)。他后面可以接不同类型的数据!
  一个变量非常重要的还有生命周期,C语言对生命周期控制很弱,命名空间也相当于作用域。

new&delete:

C语言中,如果在堆上面来操作的时候,使用的是两个函数:malloc/free,C++中添加了new/delete。

eg:
int main()
{
int *pNum = new int(100);//分配一个int类型,并将其值设置成100.
std::cout<<*pNum;
int *pArray = new int[10];//new数组
delete pNum;
delete[] pArray;
return 0;}
new/delete是一个运算符,不是一个函数

Overloaded:

重载:

  函数名可以相同,只要里面参数类型,或者类型的顺序,个数不同就不会出现函数重定义的错误,调用的时候编译器会自动判断调用哪个函数。如果函数相同,参数也相同,就会出现重定义错误。调用的时候看起来是一个函数,但实际上是多个不同的函数,也放在不同的空间。
  cout也是函数重载来做的!
  编译后这些函数是不同的,放在不同的内存空间,编译器做一一匹配,编译器会保存符号表,他通过记录了函数里面的参数类型,然后进行匹配就可以得到对应的函数。
例如:
    统一把MyCout()记录成MyCout@char()、MyCout@int()……在后面加。通过命名粉碎的机制把每个函数编的不同来实现的。
  命名粉碎后,在自己的工程下调用没问题,但别人调用的时候就不知道具体的函数名,可用加上extern "C",不让其命名粉碎,以C语言的编译方式编译。

  两个函数完全一样,但返回值不同的时候,不可以构成重载!重载只与函数名和参数有关,实际的还会加上类,命名空间等信息进去,以后会讲,这里不深究!

  重载导致的二义性:

1:为什么要重载,为了方便对函数的调用!同一功能的函数不用起不同名字。当写函数的时候可能开始不知道要传递那些参数,我们可以用重载多写几个类型的函数来解决。重载不影响运行效率,只影响编译效率。

2:匹配:当调用函数的时候传递的参数类型在重载函数里面都没有,编译器就会自动进行类型匹配。如果有对应的类型,编译器肯定会调用对应的类型。编一起的自动类型匹配是有一定规则的,自动匹配如果可以匹配的重载函数超过一个,他就可能不知道调用哪一个重载的函数了(这不符合匹配的规则),

    1:精准匹配:准确的,int到int,char到char……(有精准匹配的时候编译器就不会匹配到其他函数,也不会报错)
    2:提升匹配:char->int,float->double,short->int,long->int(所有的字面值整数都为int类型?所有的字面值浮点数都为double)。
    3:类型转换匹配:int->unsigned int(反之不行)
3:二义性:

当有多个匹配时,匹配的规则出现冲突的时候,就会出现二义性,那么就需要让他不冲突,修改成精准匹配可以解决问题!
在需要重载时,定义了太多或者太少重载函数都是不太好的!最好全用精准匹配,但写得多也可能出问题,重载的时候,最好根据业务需求来定,不能写的太多,也不能写的太少。
重载的时候,最好不要用int *等指针,容易出错。
#include <cstdio>
void MyCout(int i)
{
printf("%d\n", i);
}
void MyCout(char c)
{
printf("%c\n", c);
}
void MyCout(float f)
{
printf("%f\n", f);
}
void MyCout(char *c)
{
printf("%s\n", c);
}
int main()
{
long lNum = 100;
MyCout(lNum);
return 0;
}//这个调用就会自动匹配到int那一个函数。

头文件的三种标准:

    #include <iostream>,这个库不以.h结尾,后缀名适用于区分使用,不加.h里面的内容也是一样的。
    C++是从C语言发展而来的,里面有几个标准:1:.h结尾的,就是从C继承来的。2:前面前缀c(eg:#include <cstdio>),也是从C继承过来的,但是这个符合C++标准,他和stdio.h里面基本一样,只是cstdio符合C++标准。使用C语言的标准库应该使用cstdio。3:标准C++库(#include <iostream>)。

Default Argume默认实参:

一些函数里面的参数需要有默认值,就可以使用默认参数。例如:

#include <cstdio>
void MyCout(int i, bool line = true)
{
printf("%d", i);
if (line) printf("\n")
e4ef
;
}
void MyCout(char c, bool line = true)
{
printf("%c", c);
if (line) printf("\n");
}
void MyCout(float f, bool line = true)
{
printf("%f", f);
if (line) printf("\n");
}
void MyCout(char *c, bool line = true)
{
printf("%s\n", c);
if (line) printf("\n");
}
int main()
{
long lNum = 100;
MyCout(lNum);
MyCout(lNum, false);
MyCout(lNum, true);
MyCout("i love C++", false);
MyCout("i love C++");
return 0;
}



    如果里面每个参数都设置成有默认实参的,调用的时候就可以一个参数也不传,但如果重载了多个全都有默认实参的函数,调用的时候不传参数就会出错(不知道调用哪一个),二义性。默认实参规定出现在函数参数的最右边,或者参数排列从右到左,不能第一个参数有默认值,后面的参数没有默认值。

    解决二义性的方法1:使之唯一匹配。2:调用的时候确定他的精确参数(强制转换等方法)。

内联函数:

    在函数之前加上inline:代表我们想把这个函数做成一个内联函数,调用的时候会将这个函数展开,这样好处在于内联函数不会新建栈,他只是将函数展开,最大的好处在于运行的效率会大大的提升,坏处在于生成的exe文件体积会膨胀,代码膨胀不止来源于内联函数(在模版里面膨胀会非常大)。不会影响内存。有值返回的内联他会自动进行值的替换。但是所有写了inline的函数并不是100%会成为内联函数,会根据编译器的判断来完成,编译器认为可以展开的话才会成为内联函数(由编译器来决定,有一定的判断标准),不加inline的话肯定不会成为内联函数。
    inline函数判定标准,不同的编译器会有所不同,一般都会更具栈的使用情况来判定,inline主要提高函数调用的时间(call),分配栈的这些可以放在一个函数里面一次性分配(不然每次调用都会再次分配)。如果inline函数里面使用了大量的栈,就可能不会将这个变为一个内联函数,被判定为inline的函数一般不会使用大量的栈,调用简单(调用函数少,无递归等),不一定行数少的就可以被判定为inline,与函数关系不大,但一般都规定inline函数有一定的行数限制,超过就不要写成inline(规律)。
    宏是100%展开,inline与之的区别是inline他会进行类型的检测,使用inline,他还是一个函数,宏他只是纯粹的替换。inline函数过多,会导致编译器的判断花很长时间,编译过程耗时很长。

代码膨胀:

编译的时候首先会进行预处理过程,他会生成很多代码(展开#include,计算sizeof等……),然后会展开inline,然后会展开泛型。代码膨胀可以提高编写程序的效率,生产力会较高(java每行都可能膨胀成上十行),但代码膨胀会导致调试程序的困难,因为他是编译器生成的,出错了很难找到错误出现的地方。难以找到bug的位置。

类型转换:

C++里面新出的,C语言中,类型转换用(int)……这种方式,告诉编译器这是我知道的,就要转换。隐式转换是非常危险的(除法的时候会讲int转化为double)。C++里面有四种类型转换符,他的自由度是比C语言高些的。
    1:static_cast<>……eg:int num = static_cast<int>1.0003;//这并不是强制转换,他只是表示转换,结果是一样的,经常用于把基类指针指向派生类指针的转换。这是标准的C++风格。

    2:const_cast<>……在特定情况下使用,他可以使const变成非const,可以移除对象的常量性,但是还是不能修改内容。用于使参数进行参数的匹配。使得语法上不会有问题!一般用于指针,引用……

    3:reinterpret_cast<>……强制转换,二进制层面的转换,相当于拿着指针来拷贝(一般用于指针的转换),是比较危险的转换,最好不要用!

    4:dynamic_cast<>……类型推导转换符!在写非常大的框架的时候会用到。

这四种类型转换与C语言的属于不同的风格,C语言的转换属于暴力的方式转换,有安全和不安全,不管怎么样都会转换成功。C++中将这种转换拆分成了static_cast<>和reinterpret_cast<>,前者相对会安全一些,后者100%有问题,不安全,是开放的。

引用/指针

1:指针的缺陷是把其他数据类型改为了指针类型,引用最大的优势是使我们的状态得以保存。引用可以看成一个阉割版的指针!

2:引用加上&符号,使用引用的时候不能像指针那样赋值为nullptr,引用必须要指向一个变量。引用就是变量的别名,他是没有独立空间的!引用但凡确定之后,他的值是无法修改的。之后对引用的任何操作都是作用与引用原来的变量上。

3:指针拥有独立的空间,空间里面存储的指向的地址,指向某一个变量,指针这种是弱关联,弱关联(指向的变量可以修改),引用是强关联,他是绑定了的,无法修改(无法把这个别名给另一变量)!

eg:
int val = 100;
int num = 20;
int &refval = val;
refval = num;//正确的,相当于给val 赋值!
&refval = num;;//这个语句是错误的!右边为int,左边为地址,而求地址无法被赋值,只有地址里面的存储空间可以被赋值。
&refval = &num//也是错误的,左操作数必须为左值(可修改)。
对refval的任何操作都作用于val上,可以使用引用传递参数

void Swap(int &lhs, int &rhs)
{
int temp = lhs;
lhs = rhs;
rhs = temp;
}
这个函数可以实现两个整形数的交换,而且没有想指针那样,类型被修改了。

引用两边类型必须要一样!不可以  char &refval = val;//(val为整形)。

const int &refval = val;//这个是错误的(老师说的,但我编译可以通过,但是只能通过val来修改内存中的值,refval不能拿来修改值)!
//const引用指向的是const类型,const类型的数必须只能用const的引用(常量引用)。
const int *pval = &val;//pval值无法修改,const是左结合(优先和左边的结合),左边是什么就是什么不能变!是指针就是地址不能变。
int const *pval = &val;//值无法修改
int *const pval = &val;//地址无法修改
val的值无法再修改!

按引用传递/引用作为返回值:

1:Swap(val,num);//看起来传了一个变量进去,实际上还是相当于地址,但函数里面使用还是想使用变量一样的使用引用,里面可以进行各种各样的操作!相当于把自己传递进去了!平时都是使用指针这样操作,但引用更方便理解!引用的主要意义在于参数的传递!

2:如果想通过引用来传递参数,当你不希望函数里面修改引用的值,可以在传递的参数前面加const,这样参数就不能在这个函数里面修改,但在原来的作用域还是可以被修改的。void Swap(const int &lhs, const int &rhs)
{
int temp = lhs;
lhs = rhs;//这里和下面一排编译就不可以通过(error C3892: “lhs”: 不能给常量赋值)
rhs = temp;//
}
3:函数返回值也可以是引用!
int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
int &Index(int index)
{
return array[index];
}
int main()
{
Index(0) = 100;
std::cout << array[0];
return 0;
}

4:引用无法完全代替指针,因为引用无法指向堆空间!也永远不要那指针来取其引用(用的时候还是要加*),这样难度会提升,而且无意义!
5:引用后面必须是一个变量,不能int &val = 100;//他会在常量区分配一个空间,但毫无意义!最好不要这样用!!
6:auto:C++里面全新的概念,他会自动推导所需的类型!这种自动推导容易出问题。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  编程