通过程序来介绍
#include<iostream>
using namespace std;
int main(void)
{
cout << "This is a c++ program." << endl;
return 0;
}
1.iostream文件
iostream中的io指的是输入(进入程序的信息)和输出(从程序中发送出去的信息)。 并且c++的输入、输出方案涉及iostream文件中的多个定义。比如用来输出信息的cout就在其中。
2.头文件名的区别
C语言
C语言的传统是头文件使用扩展名 h,将其作为一种通过名称标识文件类型的简单方式。例如 math.h支持一些数学函数。
C++
C++头文件没有扩展名。 有些C头文件被转换成C++头文件,这些文件被重新命名,去掉了扩展名h,并在文件名称前面加上前缀c(表示来自C语言)
3.名称空间namespace
如果使用的是iostream,而不是iostream.h,则应使用名称空间编译指令来使iostream中的定义对程序可用,即
using namespace std;
有了这句using编译指令,才能使用cout、cin等,或者用第二种方式:
using std::cout;
using std::cin;
using std::endl;
名称空间是C++的特性之一,简单理解为:可以将自己的产品封装起来。 示例
封装性
示例: 首先定义一个头文件
在里面写上我们自己编的东西:
#pragma once
namespace AA
{
typedef int INT;
typename char CHAR;
};
然后在cpp文件中引入该头文件,但我们却无法使用之前写好的东西。
INT a会报错,因为我们只引入了头文件,没有使用里面的名称空间。 正确做法:
//c++ program
#include<iostream>
#include"AA.h"
using namespace std;
using namespace AA;
//using AA::INT;
int main(void)
{
INT a = 10;
cout << a << endl;
return 0;
}
需要第六行的该名称空间才可以使用其中的产品。或者可以用第七行这种写法来确定自己只需要哪个产品。 运行结果:
4.使用cout进行C++的输出
上面的程序有这条C++语句:
cout << "This is a C++ program." << endl;
<<符号表示该语句将把这个字符串发送给cout,该符号指出了信息流动路径。 cout是一个预定义的对象。
从概念上看,输出是一个流,即从程序流出的一系列字符。cout对象表示这种流,其属性是在iostream文件中定义的。 cout的对象属性包括一个插入运算符(<<),它可以将其右侧的信息插入到流中。
图示
指针和数组名的区别
程序示例:
#include<iostream>
using namespace std;
int main(void)
{
int a = 10;
int* p = &a;
int arr[] = { 0,1,2,3,4 };
cout << p << endl;
cout << arr << endl;
return 0;
}
这里定义了一个指针p和一个数组arr。
运行结果都是地址
反汇编查看区别
cout << p << endl;
cout << p << endl;
008F52AF mov esi,esp
008F52B1 push offset std::endl<char,std::char_traits<char> > (08F103Ch)
008F52B6 mov edi,esp
008F52B8 mov eax,dword ptr [p]
008F52BB push eax
cout << arr << endl;
cout << arr << endl;
008F52DE mov esi,esp
008F52E0 push offset std::endl<char,std::char_traits<char> > (08F103Ch)
008F52E5 mov edi,esp
008F52E7 lea eax,[arr]
008F52EA push eax
区别
在输出指针时,需要先从p里面取出四字节,再放到寄存器里push; 在输出arr时,直接把arr放到寄存器里再push。
结论
指针是变量; 数组名是一个地址——常量。
解引用
在C语言中学到,对指针解引用后得到的值就是它寸的地址对应的变量值。 可以来探索原理 程序示例
#include<iostream>
using namespace std;
int main(void)
{
int a = 10;
int* p = &a;
*p = 20;
return 0;
}
反汇编代码:
int a = 10;
000D18FF mov dword ptr [a],0Ah
int* p = &a;
000D1906 lea eax,[a]
000D1909 mov dword ptr [p],eax
*p = 20;
000D190C mov eax,dword ptr [p]
000D190F mov dword ptr [eax],14h
对于*p = 20 先从p的内存中取四个字节,即变量a的地址放入寄存器,再将20给到寄存器所存的的四字节中。完成对变量a的改变。
所以解引用的意思就是从地址中把值取出来,这里是去p的地址里取出所存的变量a的地址。
程序示例2:
#include<iostream>
using namespace std;
int main(void)
{
int a = 10, b = 20;
int* p = &a;
b = *p;
return 0;
}
反汇编代码:
int a = 10, b = 20;
000818FF mov dword ptr [a],0Ah
00081906 mov dword ptr [b],14h
int* p = &a;
0008190D lea eax,[a]
00081910 mov dword ptr [p],eax
b = *p;
00081913 mov eax,dword ptr [p]
00081916 mov ecx,dword ptr [eax]
00081918 mov dword ptr [b],ecx
对于 b = *p;
- 先去p里取出四字节放入寄存器
- 再从寄存器eax取出四字节放入寄存器ecx
- 再把ecx的内容放入到变量b的四字节中。
也可以看出:解引用这一步其实是去地址里取值的。
这样也可以得出:用一个变量赋值给另一个变量,其实也是在解引用。 示例:
#include<iostream>
using namespace std;
int main(void)
{
int a = 10;
int b;
b = a;
return 0;
}
反汇编:
int a = 10;
002D18F5 mov dword ptr [a],0Ah
int b;
b = a;
002D18FC mov eax,dword ptr [a]
002D18FF mov dword ptr [b],eax
对于 b = a; 也是从a地址里取出四字节放到寄存器,再通过寄存器给入b。
结论
解引用:到地址里去取值。
const的区别
C语言中为常变量
示例:
//const
#include<stdio.h>
int main(void)
{
const int a = 10;
int b = 100; //常量赋值
b = a; //常变量赋值
return 0;
}
两次赋值的区别:
const int a = 10;
00311825 mov dword ptr [a],0Ah
int b = 100;
0031182C mov dword ptr [b],64h
b = a;
00311833 mov eax,dword ptr [a]
00311836 mov dword ptr [b],eax
常量赋值时,是直接把值给到b的四字节中; 用const修饰的a赋值时,还是需要从a里取出四字节再赋给b。
所以C语言中const修饰的变量叫做常变量——不能作为左值。
甚至可以用指针改变它的值:
#include<stdio.h>
int main(void)
{
const int a = 10;
int b = 100;
b = a;
int* p = &a;
*p = 20;
return 0;
}
a的变化:const修饰的变量a居然能被改变
C++中的const
在C++中,const修饰的变量就是常量,和常量性质一样: 在编译期间直接将常量的值替换到常量的使用点。
示例:
int main(void)
{
const int a = 10;
int b, c;
b = 16;
c = a;
return 0;
}
反汇编代码:
const int a = 10;
00B917F5 mov dword ptr [a],0Ah
int b, c;
b = 16;
00B917FC mov dword ptr [b],10h
c = a;
00B91803 mov dword ptr [c],0Ah
可以看出,对b赋值常量是直接赋值; 对c赋值const修饰的变量a,同样是用常量赋值的。所以:
在C++中, const修饰的变量和常量性质一样,都是在编译期将常量值替换到常量的使用点。
另外 1.而且const修饰的变量必须初始化,同样因为编译期间就会替换为常量,不初始化,后面也没有机会再对其赋值。 2.如果用变量对const修饰的变量赋值,则会使其退化成常变量。
声明时const位置不同的区别
示例: const可在不同位置修饰变量
int main(void)
{
int a = 10;
int* p1 = &a;
const int* p2 = &a;
int const* p3 = &a;
int* const p4 = &a;
int* q1 = &a;
const int* q2 = &a;
int const* q3 = &a;
int* const q4 = &a;
return 0;
}
要注意的是: const与离他最近的类型结合,是该变量的类型,除了最近的类型,剩下的就是const修饰的内容。
const修饰的内容是不可作为左值。
根据上面的原理,来判断以下内容:
p1 = q1;
p1 = q2;
p1 = q3;
p1 = q4;
p2 = q1;
p2 = q2;
p2 = q3;
p2 = q4;
p3 = q1;
p3 = q2;
p3 = q3;
p3 = q4;
p4 = q1;
p4 = q2;
p4 = q3;
p4 = q4;
p1是普通指针。
对于 const int* p2和int const* p3 const修饰的类型是离他最近的类型,即int,剩下的为const所修饰的内容,所以它们两个所修饰的内容为 *p2 、*p3。
对于int* const p4 const修饰的类型为int*,那修饰的内容就是p4。
下面的四个q同理。
可以推出错误的是:
p1 = q2;
p1 = q3;
p4 = q1;
p4 = q2;
p4 = q3;
p4 = q4;
因为 *q2 和 *q3不能改变,所以把 q2/q3赋值给普通指针时,会造成普通指针来改变其中内容的后果,即 泄露常量地址给非常量指针 ,所以不能这样赋值。
p4为const修饰的内容,不能被改变。
const修饰形参
这里主要说能否形成函数重载的问题 程序示例:
int fun(int a)
{
return a;
}
int fun(const int a)
{
return a;
}
编译器并没有报错,但编译无法通过,原因如下
结论:如果const修饰的内容不包括指针,则不参与类型问题。
引用变量
之前C语言学到,&符号用来指示变量的地址。 C++给该符号赋予了另一个含义,将其用来声明引用。
示例,若我想用 A作为变量 a的别名,可以这样用:
#include<iostream>
using namespace std;
int main(void)
{
int a = 10;
int& A = a;
A = 20;
cout << a << endl;
cout << A << endl;
return 0;
}
运行示例:
通过A可以改变a的值,这就是引用。A相当于a的别名,就和鲁迅和周树人一样。。。
引用的原理
示例:
int a = 10;
int& A = a;
int* p = &a;
反汇编代码:
int& A = a;
00ED5326 lea eax,[a]
00ED5329 mov dword ptr [A],eax
int* p = &a;
00ED532C lea eax,[a]
00ED532F mov dword ptr [p],eax
可以看出:引用的实现居然和指针是一样的。 所以引用的底层是一个指针。
结论:在使用到引用的地方,编译期会自动替换成底层指针的解引用。
常问问题
- 引用为什么必须初始化?
- 引用为什么一经过初始化,就无法改变引用的方向?
答:因为只有在初始化的时候能给它赋值,其他使用到它的地方都替换成了底层指针 无法改变底层指针的指向,所以无法改变引用的方向。
3.不能将const限定的变量赋给普通引用变量:
原因是将常量的地址赋给了普通指针。
const int a = 10;
int& b = a; //错误
4.当引用一个不可以取地址的量的时候,使用常引用。
会生成一个临时量,然后常引用临时量,临时量都有常属性。 示例:
int& a = 10; //错误
const int& a = 10; //正确
动态申请空间的区别
C语言
使用malloc和free 示例:
int main(void)
{
//申请一维数组与释放
int* arr = (int*)malloc(sizeof(int) * 10);
if (arr == NULL)
return -1;
free(arr);
//申请二维数组与释放
int** brr = (int**)malloc(sizeof(int*) * 10);
if (brr == NULL)
return -1;
for (int i = 0; i < 10; ++i)
{
free(brr[i]);
}
return 0;
}
C++
int main(void)
{
//申请int类型变量
int* p = new int;
*p = 10;
delete p;
//申请int类型数组
int* arr = new int[10];
arr[0] = 10;
delete[]arr;
//申请二维数组
int** brr = new int* [5];
for (int i = 0; i < 5; ++i)
{
brr[i] = new int[10];
}
for (int i = 0; i < 5; ++i)
{
delete[]brr[i];
}
return 0;
}
new后面跟的类型就表示申请的大小。
面向过程和面向对象
C语言
面向过程语言
示例
void echo()
{
if (flag == 0)
{
printf("printf screen\n");
}
else if (flag == 1)
{
printf("printf file\n");
}
}
void Set_flag_file()
{
flag = 1;
}
void Set_flag_screen()
{
flag = 0;
}
对于这个打印函数,可以通过改变flag的值来控制其打印的结果。 但如果改变flag,也会改变其他地方调用的打印函数的结果。 所以C语言没有封装性。
C++
面向对象语言
class Note
{
public:
Note()
{
flag = 0;
}
void echo()
{
if (flag == 0)
{
printf("printf screen\n");
}
else if (flag == 1)
{
printf("printf file\n");
}
}
void Set_flag_file()
{
flag = 1;
}
void Set_flag_screen()
{
flag = 0;
}
private:
int flag;
};
使用示例:
int main(void)
{
Note n;
n.echo();
n.Set_flag_file();
n.echo();
return 0;
}
运行结果:
C语言作为面向过程语言,如果示例中的flag做出改变,会影响全局的改变。 C++作为半面向对象语言,具有封装性,若想改变示例中想打印的值,只会影响到这个模块。
end
文章内容为上课笔记,若有错误地方,请大家指正。
|