C家家

CC++的升级

 

1c++强调类型

C++强调类型,C不是很强调类型;

例如:在C++编译器中

struct  student{

const  char*  name;

Int    age;

};

这里相当于定义了一种新的student类型。可以直接用student去定义一些变量。

例如:student  student1,student2;

但是在C编译器中

struct  student{

const  char*   name;

int    age;

};

这里只是相当于定义了一组变量的集合,必须要把这种变量的集合或者说标识符重新命名,才可以用来定义变量。

例如:typedef  struct  _tag_student  student;

 

2:关于空形参问题

fun()与fun(void)之间的区别

在C++编译器中:这两者是相同的含义。都是表示返回值为int的无参函数。

所以:g()  { return  5;}这样定义是错误的,因为C++是强调类型的。但是这样对于C编译器是认可的,因为C编译器是弱类型的,括弧内没有参数表示可以接收任意多个参数。

 

3:其它内容

registerC++能取地址;C++允许随处定义变量。


C++const分析

主要内容:

1C++Cconst实现原理对比分析

2const与宏定义之间的区别

1C++Cconst实现原理对比分析

C语言中的const修饰的变量实际上还是一个变量,只是一个只读变量,是可以通过指针来改变,改变方法就是对这个const变量取地址,然后用指针指向它,去修改这个指针指向的内容,所以说C语言中const修饰的并不是真正意义上的常量,而是一个只读变量。但是有一种情况例外,就是C语言用const修饰的全局变量,编译器把它放在只读存储区里面。

 

C++里面的const修饰的变量,编译器对它的处理,是把这个放在符号表里面,符号表就是一个二维表,里面有符号和对应的值。假设采用与C语言类似的方法取地址,然后改变指针指向的内容的方法,编译器会重新分配一块空间,你的指针改变的内容并不是符号表里的值,而是另外开辟了一块空间的。所以C++里面的const修饰的的确是常量,而不是只读变量。

 

C++里面对const常量使用了extern,也会分配存储空间。

 

举个例子:

例如:const int a = 2; const int b = 5;

C编译器里面:int  arr[a+b];这样编译器会报错,因为ab实际上都是变量,两个变量怎么相加。只是一种特殊的变量。

C++编译器里面:int  arr[a+b];这样可以编译通过,因为这里C++编译器从符号表里面把这个两个东西拿出来相加,这里是真正的常量。

例如:

const int c = 0;

int* p = (int*)&c;

printf("Begin...\n");

*p = 5;

上面这个程序在C编译器里就会改变这个所谓常量的值。但在C++里却不会。

2const与宏定义之间的区别

宏定义发生在预处理阶段,不会检查作用域和类型。这就是本质的区别。

void f()

{

    #define a 3

    const int b = 4;

}

void g()

{

    printf("a = %d\n", a);

    //printf("b = %d\n", b);

}


课布尔类型和引用

主要内容:

1:布尔类型的数据类型、数值运算、赋值问题、布尔类型用途

2:三目运算符的升级

3C++中的引用

 

1布尔类型的数据类型、数值运算、赋值问题、布尔类型用途

布尔类型是C++新增的一个数据类型。

理论上编译器给它分配一个字节。

取值只有两种:truefalse

 

数值运算:

bool  b  =  0;

b++;  b = b - 3;这两项会把它作为一个字节来运算,运算结果分别是1-3,都是非0true.

 

赋值问题:

bool  b  =  false;  // 定义一个布尔类型,布尔类型名为b,值为false

int   a   =  b;    // 这里将布尔类型赋值给一个整形。a的值为0

 

b = 3;   // b是一个布尔类型,这里表示true,而不是3

a = b;   // 这里将true赋给a,所以整形a的值为1

 

b = -5;  // 这个和上面的问题类似

a = b;   // a的值为true,打印出来为1

 

上面的几个赋值:总结出来就是右值为布尔类型,赋值给左边的不管什么类型,只有两种可能:不是1就是0

 

a = 10;  // 这个a打印出来肯定是10

b = a;   // 这里打印出来是1,这个赋值没什么歧义。


2:三目运算符的升级

int  a  = 1,b = 2;

(a  <  b  ?  a : b)  =  3;

上面这个三目运算符表达式,在C编译器中,可以等价为(1  <  2  ?  1  :  2)  =  3;左值为一个常量,所以编译肯定会出错。

C++里面对这个进行了升级,这里返回值为一个引用,相当于(&a < &b ? &a : &b) = 3;所以这里的左值为一个引用变量。

这里如果这样改一下:(a < b ? 1 : b) = 3;这样修改以后,C++编译器检查值发现里面居然有一个常量,常量没办法取引用,所以编译报错。引用的意思就是给变量取一个别名,就相当于一个人有小名和大名一样。

3C++中的引用

引用的语法:Type  &name  =  var;

普通引用定义时必须用同类型的变量进行初始化。

例如:int     a  =  5;

float&  b  =  a; // 这样定义就不对了。两个类型都不同,一个是人,一个是老虎,怎么可能是一个。


课引用的本质分析

主要内容:

1:引用的赋值和概念

2:引用的本质

3:引用出错问题

1:引用的赋值和概念

1.1:概念

引用作为函数的形参时,我们可以不初始化在调用函数的时候初始化

 

引用就是给变量起了一个别名,一般必须用变量来初始化引用。但也有例外,下面会说。

编译器会给引用变量分配一块内存空间。

1.2:用变量给引用赋值

给变量a起了一个别名:

int   a  =  8;  

int&  b  =  a;  // 编译通过

 

用字面常量赋值是不对的:

int&  b  =  10;  //  编译不通过

普通引用是不能用字面常量来初始化的,只能用变量来初始化

 

const 修饰的引用叫常引用

常引用是允许用字面常量直接赋值

并且编译器会给这个引用分配4字节内存空间,该内存空间为只读:

const int& b = 10;

// b  为所分配的内存空间的别名

// &b 指向所分配的内存空间

 

常引用示例1


常引用示例2

常引用仍然可以像一般引用一样用变量赋值

int   a;

int *  p;

const  int&  b  =  a;  // b a代表的内存别名,内存变为只读

p   =  (int*)&b;

*p  =  20;           // 通过指针改变常引用b所代表的内存的内容

b   =  88;           // 报错b常引用

printf("b =  %d\n",b);  // 打印结果: 20

1.3:用字面常量给引用赋值

const 修饰的引用叫常引用

常引用是允许用字面常量直接赋值

并且编译器会给这个引用分配4字节内存空间,该内存空间为只读

例如:

int *  p;

const  int&  b  =  30;  // 常引用可以用字面常量直接赋值。

p   =  (int*)&b;

*p  =  20;             // 通过指针改变常引用b所代表的内存的内容

b   =  88;           // 报错b常引用

printf("b = %d\n",b);

这就是和const直接声明不同,const是符号表的方式,不允许改变值的。

2:引用的本质

先看一段代码:



引用是变量的别名,内部实现为指针

引用的本质就是常量指针,即指针的值不能变

例如:

int& a;          等价于   int*  const  a;

void  f(int&  a)  等价于   void  f(int*  const  a)

{   {

a  =  5;   *a  =  5;

]   }

3:引用出错问题

引用指向一个局部变量,例如:

int&  demo()

{

int  d  = 0;

return  d;

}

 

int  main()

{

int&  rd  =  demo();   //这个地方返回的是一个局部变量,

//但是这个局部变量在demo

//函数调用完以后,释放掉了,

//所以引用rd是一个释放掉的局部变量的别名。

}

 

上面这个程序可以将那个局部变量修改成static,让它不要释放。这样引用就可以指向一个真正的值了。


构造函数上

主要内容:

1:引入构造函数的目的

2:构造函数的编写方法

 

1:引入构造函数的目的

构造函数就是对类成员变量进行初始化,但是如果不用构造函数能否初始化呢?或者说我不用构造函数,默认值是不是零呢?看下面几个例子:

首先定义一个类:

class Test

{

private:

    int i;

    int j;

public:

    int getI() { return i; }

    int getJ() { return j; }

};

 

1.1:在静态存储区创建对象,成员变量初始化值为0

静态存储区创建的对象实际上就是全局对象



因为在编译的时候,会把全局变量区域也就是静态存储区的所有变量全部初始化为0

访问方法:gt.getI()gt.getJ()

1.2:在栈区创建对象,成员变量的初始值为随机数


在栈区创建对象就是定义一个局部变量。因为在栈区,没有经过编译器的初始化阶段,所以初始值肯定是一个随机数。是一个乱码。

1.3:在堆区创建对象,成员变量的初始值为随机数


分配和释放的方法,这里得到依旧是随机数。原因和栈区一样。

 

上面的说法是无法初始化,就是初始化也不可能有默认值。假设我在类里面定义一个初始化函数,然后在初始化之前就调用他,这种方法是否可行。下面做一个实验试试:

 

1.4:自己定义一个初始化函数来尝试初始化





调用初始化函数以后,要马上使用。不然就会出现随机值。

 

鉴于上面四种情况,引入了构造函数的概念。

 

2:构造函数的编写方法

构造函数的特点:

a:没有任何返回类型的声明。

b:构造函数在对象定义时候被自动调用。

c:名字和类名相同。

d:构造函数可以重载

e:构造函数不一定是public

f:构造函数可以有return



这个类里面的Test()函数就是一个构造函数。


第六课构造函数

主要内容:

1:带参数的构造函数

2:赋值和初始化的区别

3:定义和声明的区别

4:构造函数的调用问题,可以手动调用

 

1:带参数的构造函数

就是构造函数里面有形式参数,例如


这就是构造函数的重载,这个没什么好说的。

 

2:赋值和初始化的区别

初始化意味着要调用构造函数,而赋值不会。看下面几个例子:

int  i;这个i已经完成了初始化,如果是在静态变量区,会被编译器赋值为0,前面已经说过。但是它初始化的值是一个随机值。

i = 8; 这里执行的是赋值操作。不是初始化。

 

int  i  = 8;这种相当于初始化。

 

C++中,看下面的例子:

    Test  t;      // 调用 Test()

    Test  t1(1);  // 调用 Test(int v)

    Test  t2 = 2; // 调用 Test(int v)   这几个会调用构造函数,因为属于初始化。

但是:

t  =  t2;这里直接把t2这个对象赋值给t这个对象,这样不会去调用构造函数,因为不是初始化。

3:定义和声明的区别

定义指的是申请对象并调用构造函数,例如Test  t;

声明指的是告诉编译器存在这样一个对象,例如extern  t;

定义声明最重要的区别:

定义创建了对象并为这个对象分配了内存,声明没有分配内存.

 

4:构造函数的调用问题,可以手动调用

构造函数自动调用不用说了,就是定义一个对象的时候自动去调用构造函数。但是假设要你定义一个数组对象,并且想要初始化好数组里的初值。看下面这个例子

 



Test  ta[3];像这样定义了一个数组对象ta,有三个元素,但是初始值都为0。但是我想指定初始值,就只能手动去调用构造函数。调用方法如下:

Test  ta[3]  =  {Test(), Test(1), Test(2)};  这样就可以了。

 

在单独定义一个对象也可以手动调用,例如:Test  t  =  Test(100);

第七课构造函数

主要内容:

1:两个特殊的构造函数

2:拷贝构造函数的意义

 

1:两个特殊的构造函数

一个是无参构造函数

一个是拷贝构造函数

在没有手工定义任何构造函数的情况下,编译器会提供一个无参构造函数。这里说的是任何构造函数也包括拷贝构造函数。

 

2:拷贝构造函数的意义

C语言里一种很简单的赋值方式:

int  i  =  5;  这里完成变量i的定义和初始化。

int  j;

j  =  i;  这里完成赋值了。

C语言可以赋值,那C++呢,肯定也可以赋值的。

例如:



    Test  t1;

    Test  t2  =  t1; //这里如果类里面没有定义拷贝构造函数,那么编译器就会有一个自定义的拷贝构造函数,完成浅拷贝。浅拷贝的结果是物理状态相同。

也可以这样调用Test  t2(t1);

 

物理状态的意思是:我一个对象里的值原来是什么值,拷贝给另一个对象以后,还是那个值。我一个对象里的指针指向哪块内存,那么另外一个对象的指向也指向那块内存。




在释放的时候,就会报错了。因为同一块内存被释放了两次

 

而深拷贝考虑的是逻辑状态相同。它这里的意思是保证逻辑值相同,但不保证指针一定都是指向一块堆内存这种物理状态。但是保证指针指向的内存里存储的数据是一样的。

 



一般性的原则,定义拷贝构造函数就定义成深拷贝。


初始化列表的使用

 

主要内容:

1:初始化列表的引入

2:初始化列表的一些问题

 

1:初始化列表的引入


上面这个例子的cl是一个const成员变量,不能出现在赋值符号左边。但是直接定义又会说const成员变量没有赋值。于是初始化列表就出场了。

 

还有下面这种情况:定义一个类,然后在另外一个类里去定义这个类的对象,要完成初始化,也要用到初始化列表。



2:初始化列表的一些问题

2.1:初始化顺序的问题

初始化的顺序与成员的声明顺序相同,与初始化列表中的位置无关。例如上面的这个实例,它的初始化顺序应该是m2m3m1。而不是列表中的m1m2m3

2.2:初始化列表先与构造函数的函数体执行

这句话不用解释了吧。

 

2.3:类中的const成员

类中的const成员会被分配空间,分配空间好理解。

类中的const成员本质是只读变量,这句话的意思是说它是一个变量,没有定义到符号表。可以通过指针改变const变量的值。

类中的const成员只能通过初始化列表来完成初始化。这句话的意思是因为const是一个只读变量,所以不能出现在赋值符号左边。所以必须用初始化列表来完成初始化。

 

修改const只读变量的方法如下:



第九课对象的构造顺序

主要内容:

1:局部对象的构造顺序依赖于程序执行流,假设程序执行流突然goto了,或者其它问题到导致程序执行流被打断了。有可能出现对象没有产生,使用对象导致程序崩溃的问题。

2:堆对象的构造顺序依赖于new的使用顺序。

3:全局对象的构造顺序不确定,尽量的减少全局对象的相互依赖

 

第十课对象的销毁

主要内容:

大概的内容就是析构函数的引入,就是用户可能不记得去调用释放,导致内存泄露,所以引入了析构函数。另外就是析构函数不能重载,因为析构函数没有函数参数。

 

第十一课神秘的临时对象

主要内容:

1:临时对象的引入

2:具体实践

1:临时对象的引入

临时对象是直接调用构造函数,就会产生一个临时对象。但是会马上消失。



2:具体实践


这里C++编译器不会生成临时对象,但是还是推荐使用Test t = 10;这种情况是绝对不会生成临时对象的。因为临时对象会降低效率,也是bug的根源。











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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

奔跑吧撸码兄弟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值