有很多初学者在初学C++的时候很容易遇倒麻烦,基本上都是C++里面的基础知识越是基础知识越不容易搞懂,也有一些是matlab和IDL方面的,后两者不够典型。今天就来简单的说下这些内容吧。
有兴趣一起交流学习c语言c++编程的同学可以加群:466 572 167一起交流
编程最重要的是实践,就是写代码、看别人写的代码、再写代码,你看再多的书,不写代码,你还是不会编程。但是写代码也要讲究方法循序渐进,不能刚学了几天语法,就想写个操作系统什么的,树立这种不切实际的目标只会让你的自信受到严重打击迷失自我。
数据bit在内存中的存储顺序
我们平时所用的数据,不管是int型、char型、double型等等,最终都是要变成一系列的0和1放到内存里面的。比如char s=‘’,它要首先转换成二进制码00000001 ,共8bit,然后再把这些0和1放到内存里面。说白了,就是要用一些列的低电压和高电压去电内存条或寄存器(这么说不太恰当,为了便于理解,姑且这么说吧),对其进行磁化,0代表低电压,1代表高电压,二者对内存的磁化程度不同,因此就记录了这么一系列0和1,也就记录了‘’这么一个字符。
但是,这些0和1放到内存里面的时候是有顺序的,很多人就死在了这个顺序上面。计算机是按照从低位到高位的顺序存放的,也就是说我们将‘’转换成00000001的时候是按照我们人的习惯书写的,这些0和1放到内存里面的时候实际上是10000000。有人要窥探double型数据的二进制码,由于忽略了这种存储顺序,就顺手拈来下列代码:
这段代码的思想还是很不错的,抛开了二进制与十进制的转换规则,直接去查看double型数据的内存里的东西,但是很显然它得不到想要的结果。举个例子 ,输入19.2。
19.2按相应规则转换成二进制为0100000000110011001100110011001100110011001100110011001100110011,它放到内存里面应该是1100110011001100110011001100110011001100110011001100110000000010。按照上面的代码,先取出第一个8bit,按照人的书写习惯从高位到低位将其输出,得到00110011;再取出第二个8bit,从高位到低位将其输出,得到00110011......最后,必然会得到这样的一系列0和1的字符串0011001100110011001100110011001100110011001100110011001101000000,显然这与19.2的二进制码是不符的。能够达到目的的代码可以是这样的:
有兴趣一起交流学习c语言c++编程的同学可以加群:466 572 167一起交流
仅在第一层的循环条件中做一下改动就可以了。
sizeof和strlen
这个问题是一个老生常谈的问题,实际上它是C里面的典型问题。从定义上来讲,二者有着根本区别,sizeof是一个操作符,而strlen是一个函数。 既然strlen是一个函数,那么它就有特定的参数,它的参数就是char*类型的数据,也就是C-串。这种字符串要求有一个‘’作为结尾,而strlen返回的是从字符串开始到‘’之前的字符个数。比如char* str=“abc”;int n=strlen(str);那么n的值就是3,但是实际上该C-串有4个字符组成,即:‘a’、‘b’、‘c’、‘’。如果定义一个字符数组char s[3];s[0]=‘a’、s[1]=‘b’、s[2]=‘c’,由于该数组中缺少结尾标志‘’,是不能当作C-串来用的。C++相对于C,出现了新的相关类型string,它也是不能直接当作C-串来用的,幸运的是每一个string对象都可以通过其成员函数c_str来获得一个const char*型的C-串。
sizeof是一个操作符,它获得的是数据类型的长度,以字节为单位。例如int n,sizeof(int)与sizeof(n)是等效的,其值在主流的软硬件环境下都是4。在这里有必要说一下指针。指针是一种高级的数据类型,从大类上来讲,不管是指向哪种数据类型的指针,它都是指针,它所代表的内存块儿中存放的都是地址值,该地址值用4个字节的整数来表示,相当于一个整型数据,因此sizeof(int*)=sizeof(char*)=sizeof(int)=4;但是指针又细分为指向不同数据类型的指针,比如int* p1;char* p2,p1与p2又是不同的,主要表现在指针的移动方面,p1++移动了4个字节,p2++移动了一个字节。指针的定义既要表明该变量是一个指针,又要表明该变量所指向的数据类型。对p1来讲,*表示p1是一个指针,int表示p1指向了整型数据。
数据类型
一说到数据类型,很多人都会自然而然地想到int、double、float等大多数教材上明确告诉我们的数据类型,殊不知,C++中有很多隐式的数据类型。在这里,我教给大家一个判断数据类型的方法:在一个单独变量的定义语句中,去掉变量名,剩下的部分就是该变量的数据类型。比如int n,去掉变量名n,剩下的int就是n的数据类型;再比如int a[5],去掉变量名a,int[5]就是变量a的数据类型,这是一个含有5个整型数据的数组,这就是我所说的隐式的数据类型之一。类似的还有int(*)[5](指向数组的指针)、int*[5](存放整型指针的数组)、
int(*)(param1,param2,param3......)(指向函数的指针)等,这些都是隐式的数据类型。或许你认为把数组当作一种数据类型难以接受,但是如果你能够透彻理解这些概念的话,很多问题都会迎刃而解。比如,我在上面讲到,sizeof得到的是数据类型的长度,在int a[5]前提下,
sizeof(a)的值为20就不难理解,因为int[5]是一种数据类型,占据5x4=20个字节的长度,sizeof获取的正是这种数据类型的长度。
蜕变
蜕变说的是某些变量名在特定情况下自动隐式转换为相应指针(地址值)的现象。常见的蜕变现象发生在数组名和函数名当中,比如
int a[5],int* p=a,此时a就蜕变成了数组第一维的首地址(第一维第一个元素的地址)&a[0],再比如,sizeof(a)得到的值是20,把a作为函数void func(int n[5]){cout
指针与数组
数组是一种非常特殊的数据类型,它占据一些列连续的内存块儿,对数组进行的操作依靠地址的偏移来完成,数组名本身代表数组这个整体,但是数组名又都能够发生蜕变。这往往使得很多人对数组驾驭不住,但如果你能透彻的理解它,彻底的掌握它,数组又显得活灵活现,应用起来如鱼得水。比如int a[5][5][5],由于数组名a的蜕变,使得a与&a[0]在某些情况下是等效的。更为特殊的是对于一个数组来讲,整个内存块儿的首地址 和第一维的首地址、第二维的首地址、第三维的首地址......最后一维的首地址都是相等的,即:&a=&a[0]=&a[0][0]=&a[0][0][0],由于蜕变,又使得a=&a[0],所以a=&a=&a[0]=&a[0][0]=&a[0][0][0]。但是,这五者的含义是不同的,它们分别向编译器传达了不同的信息。我在上面讲到,判断数据类型就是在定义语句中把变量名去掉,那么a的类型就是int[5][5][5],如果结合指针来理解,&a就应该用int(*)[5][5][5]型的指针(指向数组int[5][5][5]的指针)来接收,int(*p)[5][5][5]=&a;p指向的是整个内存块儿,p++(如果存在的话)一次性移动了125x4个字节;同理,a[0](a[1]、a[2]、a[3]、a[4])的数据类型是int[5][5],a[0][0]的数据类型是int[5],a[0][0][0]的数据类型是int。需要注意的是,由于蜕变,从地址值方面来讲,a=&a[0],所以int(*p)[5][5]=a和int(*p)[5][5]=&a[0]是等效的。因此类似这样的表达式int**p=new int[5][5]是不正确的,因为数据类型不匹配。
几个名词
数组指针:指向数组的指针,比如int(*p)[5],p的数据类型为int(*)[5],p指向一个含有5个整型数据的数组,p一次性移动5x4=20个字节;
指针数组:存放指针的数组,比如int* p[5],p为数组名,该数组能存放5个指向整型数据的指针;
函数指针:指向函数的指针,比如void (*p) (void),p的数据类型为void (*) (void),p指向一个类型为void (void)的函数;
指针函数:返回指针的函数,比如int* p(void),p为函数名,该函数返回一个指向int型的指针。
有兴趣一起交流学习c语言c++编程的同学可以加群:466 572 167一起交流
效率问题
有的人喜欢用递归函数来完成某些循环运算,我的建议是递归函数能不用就不用。这要从函数的调用说起,函数1调用函数2的时候,所做的工作有:1、为函数2建栈,2、为函数1维护状态例程保护指针,3、为函数1维护返回入口,4、为函数2预留返回值空间,5、传参数,6、拷贝函数2的返回值 ,7、返回函数1等步骤。其中1,2,3,7都是与运算功能无关的额外开销,而递归函数就是函数间的连续循环调用,可见其无用功量之大,效率之低。
最后说一句,程序质量和运行效率是程序的两条命脉,把握好空间和时间的平衡,做到少占内存快点跑,菜鸟也能变老鸟。
领取专属 10元无门槛券
私享最新 技术干货