从C到C++

1、逻辑型

也称布尔型,其取值为true(逻辑真)和false(逻辑假),存储字节数在不同编译系统中可能有所不同,VC++中为1个字节。

声明方式:

bool result;

result=true;

可以当作整数用(true一般为1,false为0)

把其它类型的值转换为布尔值时,非零值转换为true,零值转换为false

#include<iostream>
using namespace std;
int main()
{
	bool result;
	result = true;
	cout<<result<<endl;
	return 0;
}

把其他类型值转换为布尔值时,非零值转换为true,零值转换为false(比如把result = 100,系统会有警告:会有一个截断)


2、const

用const给常量起个名字(标识符),这个标识符就称为标识符常量;因为标识符常量的声明和使用形式很像变量,所以也称常变量

定义的一般形式:

const 数据类型 常量名=常量值;

数据类型 const 常量名=常量值;

例如:

const  float  PI=3.14159f;

注意事项:

常变量在定义时必须初始化;

常变量初始化之后,不允许再被赋值;

#include<iostream>
using namespace std;
int main(void)
{
    //const int a;  Error,这是一个常量 必须初始化
    const int a = 100;
    //a = 200;      Error,常量不能重新被赋值
    return 0;
}
const可以作用于指针变量

#include<iostream>
using namespace std;
int main()
{
	int b = 100;
	int a = 50;
	const int *p;//*p所指向的内容是不变的 可以不必初始化
	//等价于int const *p
	p = &b;
	//*p = 200;
	cout<<*p<<endl;
	int *const p2 = &b;//指针指向的方向是固定的 不能改变
	*p2 = 200;//但是所指向的内容是可以改变的
	cout<<*p2<<endl;
	return 0;
}
const与define的区别:

1、const定义的常量有类型,而#define定义的没有类型,编译可以对前者进行类型安全检查,而后者仅仅只是做简单替换

2、const定义的常量在编译时分配内存,而#define定义的常量是在预编译时进行替换,不分配内存

3、作用域不同,const定义的常变量的作用域为该变量的作用域范围。而#define定义的常量作用域为它的定义点到程序结束(当然可以用undef结束)

注:1定义常量还可以用enum,尽量用const、enum替换#define定义常量。


enum用法:


enum box{pencil,pen};定义了枚举变量Box,变量内的元素称为枚举元素


定义:enum box box2; //box box2

或者在声明的时候直接定义 enum {pencin,pen}box,box2;


枚举变量中的枚举元素系统是按照常量来处理的,故叫枚举常量,他们是不能进行普通的算术赋值的,(pencil=1;)这样的写发是错误的,但是你可以在声明的时候进行赋值操作!

enum box{pencil=1,pen=2};

但是这里要特别注意的一点是,如果你不进行元素赋值操作那么元素将会被系统自动从0开始自动递增的进行赋值操作,说到自动赋值,如果你只定义了第一个那么系统将对下一个元素进行前一个元素的值加1操作,例如

 enum box{pencil=3,pen};//这里pen就是4系统将自动进行pen=4的定义赋值操作!

#include <iostream>  
using namespace std;  
  
void main(void)  
{  
    enum egg {a,b,c};  
    enum egg test; //在这里你可以简写成egg test;  
    test = c; //对枚举变量test进行赋予元素操作,这里之所以叫赋元素操作不叫赋值操作就是为了让大家明白枚举变量是
	//不能直接赋予算数值的,例如(test=1;)这样的操作都是不被编译器所接受的,正确的方式是先进行强制类型转换例
	//如(test = (enum egg) 0;)!  
    if (test==c)  
    {  
        cout <<"枚举变量判断:test枚举对应的枚举元素是c" << endl;  
    }  
    if (test==2)  
    {  
        cout <<"枚举变量判断:test枚举元素的值是2" << endl;  
    }  
    cout << a << "|" << b << "|" << test <<endl;  
    test = (enum egg) 0; //强制类型转换  
    cout << "枚举变量test值改变为:" << test <<endl;  
}


2高层次编程 用const enum inline 替换#define

3在底层编程(比如说框架类的编程) define很灵活

#include<iostream>
using namespace std;
  
#define STR(a) #a
#define CAT(a,b) a##b
int main(void)
{
    int xy = 100;
    cout<<STR(ABCD)<<endl;   //#ABCD<=>"ABCD"
    cout<<CAT(x,y)<<endl;   //x##y <=>xy
    return 0;
}


#define定义的常量,容易产生副作用。 容易有莫名其妙的问题

#include<iostream>
using namespace std;
//Effective C++ 3rd的一个例子。
#define CALL_WITH_MAX(a,b) ((a) > (b) ? (a) : (b))

int main()
{
	int a = 5;
	int b = 0;
	CALL_WITH_MAX(++a, b);  //a被累加二次
	cout<<a<<endl;
	CALL_WITH_MAX(++a, b+10);   //a被累加一次
	cout<<a<<" "<<b<<endl;
}
//在这里,调用f之前,a的递增次数竟然取决于“它被拿来和谁比较”


3、结构体对齐

编译器为每个“数据单元”按排在某个合适的位置上。

C、C++语言非常灵活,它允许你干涉“内存对齐”

为什么要对齐?性能原因:在对齐的地址上访问数据快。

如何对齐?第一个数据成员放在offset为0的位置

其它成员对齐至min(sizeof(member),#pragma pack所指定的值)的整数倍。

整个结构体也要对齐,结构体总大小对齐至各个成员中最大对齐数的整数倍。


#include<iostream>
using namespace std;
#include<stdio.h>//为了进行地址的正确输出
struct Test
{
    char a;
    double b;
};
   
//第一个成员与结构体变量的偏移量为0
 //其他成员要对齐到某个数字(对齐数)的整数倍地址
 //对齐数取编译器预设的一个对齐整数与该成员大小的较小值
int main(void)
{
    Test test;
    //&test = &test.a
    char *p = (char *)&test;
    printf("p = %p\n",p);
    //cout<<p<<endl;
    p = &test.a;
    printf("p = %p\n",p);
    //cout<<p<<endl;
  
    cout<<sizeof(Test)<<endl;
    return 0;
}

可以直接到编译器中去修改预设的对齐数 

如果改为4  则对齐数为4 那么结构体的大小为多大呢??   12


如果结构体多了char c  那么结构体大小为多大呢?结构体总大小为最大对齐数的整数倍  16

总结:
1第一个成员与结构体变量的偏移量为0
2其他成员要对齐到某个数字(对齐数)的整数倍地址
3对齐数取编译器预设的一个对齐整数与该成员大小的较小值
4结构体总大小为最大对齐数的整数倍 
关于结构体对齐,编程时可以考虑下如何把结构体设计的最小,不用有太多的便宜对齐!

4、域运算符

C++中增加的作用域标识符 ::

用于对与局部变量同名的全局变量进行访问

用于表示类的成员,这将在关于类的一节中详细说明

#include<iostream>
using namespace std;  
int var = 100;  
int main(void)
{
    int var = 50;
    cout<<var<<endl;
    cout<<::var<<endl;//可以访问全局变量
    return 0;
}

5、new运算符

new运算符可以用于创建堆空间

成功返回首地址 失败直接会抛出异常(所以没必要与NULL进行比较)

语法:

指针变量=new 数据类型;

指针变量=new 数据类型[长度n];

例如:

int *p; p=new int;

char *pStr=new char[50];

delete运算符 可以用于释放堆空间

语法:

delete 指针变量;

delete [] 指针变量;(如果没有[] 结果是不确定的)

例如:

delete p;

delete [] pStr;

#include<iostream>
using namespace std;
int main(void)
{
    int *p = new int;  //分配一个整数空间 4字节
    //可以进行初始化 int *p = new int(33);  如果是[]是分配多个空间
    /*
    如果是char *p = new char;
    打印的话要用printf("p = %p\n",p);
    */
    cout<<p<<endl;
    int *p2 = new int[10];//分配连续的10个整数空间 40字节
    delete p;
    delete []p2;
    return 0;
}
new在类中的应用:

new一个新对象的作用有:1、 内存分配(operator new)    2 、调用构造函数

delete释放一个对象:1、调用析构函数  2、释放内存(operator delete)

new实际有3种用法:

new operator 分配内存+调用构造函数

operator new 只分配内存

placement new 不分配内存、仅仅只是调用了拷贝构造函数  (后面详细进行解释)


6、重载

相同的作用域,如果两个函数名称相同,而参数不同,我们把它们称为重载overload

函数重载又称为函数的多态性  (这是一种静态的多态  在编译时刻就确定函数了 而动态是在运行时才确定的,称为静态联编、动态联编)

函数重载不同形式:1、形参数量不同 2、形参类型不同 3、形参的顺序不同 4、形参数量和形参类型都不同

调用重载函数时,编译器通过检查实际参数的个数、类型和顺序来确定相应的被调用函数

#include<iostream>
using namespace std;
//类型不同  也可以构成重载
void fun(int a,int b)
{
    cout<<"int fun"<<endl;
}
  
void fun(double a,double b)
{
    cout<<"double fun"<<endl;
}
  
int main(void)
{   
    fun(3,4);
    fun(3.3,4.4);
    return 0;
}

合法的重载例子:

int  abs(int i);

long abs(long l);

double abs(double d);

非法的重载例子:

int  abs(int i);

long abs(int i);

void abs(int i);

//如果返回类型不同而函数名相同、形参也相同,则是不合法的,编译器会报"语法错误"。重载的时候编译器是不会检查返回值的

其实重载的实质是 name managling

这里把它翻译为名字改编。

C++为了支持重载,需要进行name managling

#include<iostream>
using namespace std;  
//类型不同  也可以构成重载
void fun(int a,int b)
{
    cout<<"int fun"<<endl;
}  
void fun(double a,double b)
{
    cout<<"double fun"<<endl;
} 
void fun(int a); 
int main(void)
{    
    fun(3,4);
    fun(3.3,4.4);
    fun(3);
    return 0;
}
此时编译器会报错,  无法解析的外部符号 "void __cdecl fun(int,int)" (?fun@@YAXHH@Z),该符号在函数 _main 中被引用

7、extern C

extern “C”实现C与C++混合编程
#include<iostream>
using namespace std; 
//类型不同  也可以构成重载
void fun(int a,int b)
{
    cout<<"int fun"<<endl;
}
  
void fun(double a,double b)
{
    cout<<"double fun"<<endl;
}
  
//extern "c"表示不进行名字改编
extern  "C" void fun(int a)
{
    cout<<"xxxxxx"<<endl;
}
  
extern "C" void fun(double a)
{
    cout<<"yyyyy"<<endl;
}
  
int main(void)
{
      
    fun(3,4);
    fun(3.3,4.4);
    fun(3);
    return 0;
}

此时会报错error C2733: second C linkage of overloaded function 'fun' not allowed

因为C是不支持两个函数同名的


为了更加的通用:

#ifdef __cpluscplus
extern "C"
{
#endif
    void fun(int a);
    void fun2(double a);
#ifdef __cpluscplus
}
#endif
//就是在extern "C"{ 与 }之间插入判断


8、带默认参数的函数

函数声明或者定义的时候,可以给形参赋一些默认值

调用函数时,若没有给出实参,则按指定的默认值进行工作

#include<iostream>
using namespace std;
//默认参数必须是从右往左赋值
int fun(int a,int b = 5)
{
    return a + b;
}
int main(void)
{
    cout<<fun(3)<<endl;
    return 0;
}

函数没有声明时,在函数定义中指定形参的默认值

函数既有定义又有声明时,声明时指定后,定义后就不能再指定默认值

#include<iostream>
using namespace std;
  
int fun(int a,int b = 5)
{
    return a + b;
}
//这个是已经声明的函数
int fun(int a,int b,int c = 6);
  
int main(void)
{
    cout<<fun(3)<<endl;
    cout<<fun(3,4)<<endl;
    return 0;
}
//以下是定义 不需要给定默认值了
int fun(int a,int b,int c)
{
    return a + b + c;
}
形参如果有默认值,那么函数容易产生二义性
int add(int x=5, int y=6);
int add(int x=5, int y=6, int z=7);
int main() {
    int sum;
    sum= add(10,20);
    return 0;
}

9、引用

变量有名称和空间

而引用仅仅是变量的别名  引用没有自己的空间

引用要与它所引用的变量共享空间

对引用所做的改变实际上是对它所引用的变量的改变


定义引用的一般格式:

类型  &引用名 = 变量名;

例如:int a=1;

      int  &b=a; 

// b是a的别名,因此a和b是同一个单元

注意:定义引用时一定要初始化,指明该引用变量是谁的别名

在实际应用中,引用一般用作参数传递与返回值


引用一经初始化  不能重新指向其它变量 如下例   仅仅是将val2赋值给erfval  不像指针一样重新指向

#include<iostream>
using namespace std;
 
int main(void)
{
    int val = 100;
    int &refval = val;
    refval = 200;
    cout<<val<<endl;
    int val2 = 500;
    refval = val2;//不代表将refval引用至val2这个变量 仅仅只是代表将val2赋值给refval
    cout<<val<<endl;
    return 0;
}

const引用是指向const对象的引用

const int ival = 1024;

const int& refVal = ival; //ok:both reference and object are const

int &ref2 = ival; //error:nonconst reference to a const object


注:const引用可以指向普通变量  但是值不能够更改

int val2 = 1024;

const int &ref3 = val2;//这是允许的


按引用传递

引用传递方式是在函数定义时在形参前面加上引用运算符"&"

例如:swap(int &a,int &b);

按值传递方式容易理解,但形参值的改变不能对实参产生影响

地址传递方式通过形参的改变使相应的实参改变,但程序容易产生错误且难以阅读

引用作为参数对形参的任何操作都能改变相应的实参的数据,又使函数调用显得方便、自然


引用作为返回值

引用的另一个作用是用于返回引用的函数

函数返回引用的一个主要目的是可以将函数放在赋值运算符的左边

(赋值运算符一般是放在右边的)

#include<iostream>
using namespace std;
//引用作为函数的返回值
 
int a[] = {0,1,2,3,4}; 
int &index(int i)
{
    return a[i];
}
int main(void)
{
    index(3) = 100; //引用作为函数返回值,使得函数可以放在赋值运算符左边
    cout<<a[3]<<endl;
    return 0;
}


注:引用在函数返回的时候进行初始化!!注意,是函数返回 如果是局部变量,返回后就不见了,这个是非常重要的东西

不能返回对局部变量的引用。

因为引用在函数返回的时候进行初始化,如果是一个局部变量函数返回时,局部变量已经不存在了,所以这是不行的

#include<iostream>
using namespace std;
 
int &add(int a,int b)
{
    int sum;
    sum = a+b;
    return sum;
}
 
int main(void)
{
    //竟然会影响到n2的值 而不会影响n的值
    //因为n是个变量,函数返回的时候能够捕捉到
    //而n2是引用,没有自己独立的地址空间,所以n2依赖于它所引用的变量
    //如果n2所引用的变量的生命期结束了,也就是说n2是一个无效的引用,那么n2的值将是不确定的。
    //第一次成功是因为内容还没有被覆盖  而接下来被n所覆盖了  所以接下来输出的值是不确定的
    int n = add(3,4);
    int & n2 = add(5,6);
    //cout<<"n2 = "<<n2<<endl;
    cout<<"n = "<<n<<endl;
    cout<<"n2 = "<<n2<<endl;
}

引用与指针区别
1、引用访问一个变量是直接访问,而指针是间接访问。
2、引用是一个变量的别名,本身不单独分配自己的内存空间,而指针有自己的内存空间。
3、引用一经初始化不能再引用其它变量,而指针可以。、
4、尽可能使用引用,不得已时使用指针。



值传递   实参要初始化形参要分配空间,将实参内容拷贝到形参
引用传递  实参初始化形参时不要分配空间
指针传递 本质是传递,实参初始化形参的时候,也是要分配空间的,分配的是4字节的空间(32位系统) 如果我们要修改指针的地址,单纯用指针传递是不行的,只能用**  或者&&
所以要尽可能使用引用  不得已使用指针


10、内联函数

当程序执行函数调用时,系统要建立栈空间,保护现场,传递参数以及控制程序执行的转移等等,这些工作需要系统时间和空间的开销。有些情况下,函数本身功能简单,代码很短,但使用频率却很高,程序频繁调用该函数所花费的时间却很多,从而使得程序执行效率降低。
为了提高效率,一个解决办法就是不使用函数,直接将函数的代码嵌入到程序中。但这个办法也有缺点,一是相同代码重复书写,二是程序可读性往往没有使用函数的好。
为了协调好效率和可读性之间的矛盾,C++提供了另一种方法,即定义内联函数,方法是在定义函数时用修饰词inline
//在编译时候已经展开了 让编译器做了  而不是程序员来做
inline intmax(int a, int b)
{
  return a > b ? a : b;
}
#define MAX(a, b) (a) > (b) ?(a) : (b)
//本质都是展开的  并不涉及函数调用

宏与内联函数的区别:
1、内联函数调用时,要求实参和形参的类型一致 , 另外内联函数会先对实参表达式进行求值,然后传递给形参 ; 而宏调用时只用实参简单地替换形参。( 类型检查)
2、内联函数是在编译的时候、在调用的地方将代码展开的,而宏则是在预处理时进行替换的
3、在C++中建议采用inline函数来替换带参数的宏
总结 :常量  用const     带参数的宏(类似于函数调用)    inline

11、类型转换

旧式转型
(T)expr
T(expr)

新式转型
const_cast<T>(expr)
static_cast<T>(expr)
reinterpret_cast<T>(expr) 
dynamic_cast<T>(expr)
执行“安全向下”转型操作,也就是说支持运行时识别指针或所指向的对象,这是唯一个无法用旧式语来进行的转型操作。


用来移除对象的常量性(castaway the constness)
const_cast一般用于指针或者引用
使用const_cast去除const限定的目的不是为了修改它的内容
使用const_cast去除const限定,通常是为了函数能够接受这个实际参数

#include<iostream>
using namespace std;
int main(void)
{
    const int val = 100;
    int n = const_cast<int>(val);
    return 0;
}
//因为没有任何意义  所以编译器不让通过
//const_cast一般来说 用指针或者引用


#include<iostream>
using namespace std;
 
void fun(int &val)
{
    cout<<"fun "<<val<<endl;
}
 
int main(void)
{
    const int val = 100;
    //int *p = &val;  Error,无法从const int*转换为int *
    int *p = const_cast<int *>(&val);
    //
    *p = 200;
    //并没有发生改变  可能生成了一个临时对象  操作的是临时变量的空间
    //常量一定是不能更改的
    //注:整型的地址应该是可以打印的
    cout<<&val<<endl;
    cout<<p<<endl;
    cout<<"val = "<<val<<endl;
 
    //接下来就是引用!
    const int val2 = 200;
    //int &refval2 = val2;  Error
    int &refval2 = const_cast<int&>(val2);
    refval2 = 300;
    cout<<"val2 = "<<val2<<endl;  //不会改变常量val2的值,也是生成一个临时空间
    fun(const_cast<int &>(val2));
}


编译器隐式执行的任何类型转换都可以由static_cast完成
当一个较大的算术类型赋值给较小的类型时,可以用static_cast进行强制转换。
可以将void*指针转换为某一类型的指针
可以将基类指针转成派生类指针
无法将const转化为nonconst,这个只有const_cast才可以办得到

#include<iostream>
using namespace std;
int main(void)
{
    int n = static_cast<int>(3.14);
    cout<<"n = "<<n<<endl;
    void *p = &n;
    int *p2 = static_cast<int *>(p);
}

注:
隐式转换:编译器可以自动完成的转换(一般来说安全的)  
int a;short b;a = b;(精度低的给高的  一般不会出错)
显示转换:也称为强制转换,精度高的给低的,必须用显示转换





































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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值