java学习路线图:完整的学习C++的读书路线图

/*
推荐给想学C朋友个简单但是完整学习C读书路线图:

C Primer-> c标准库-> effective C-> effective STL->深入探索C对象模型

C常用算法源码

算法(Algorithm):计算机解题基本思想思路方法和步骤算法描述:是对要解决
个问题或要完成项任务所采取思路方法和步骤描述包括需要什么数据
(输入什么数据、输出什么结果)、采用什么结构、使用什么语句以及如何安排这些语句等
通常使用自然语言、结构化流程图、伪代码等来描述算法

、计数、求和、求阶乘等简单算法

此类问题都要使用循环要注意根据问题确定循环变量初值、终值或结束条件
更要注意用来表示计数、和、阶乘变量初值

例:用随机产生100个[099]范围内随机整数统计个位上数字分别为
1234567890个数并打印出来

本题使用来处理a[100]存放产生确100个随机整数
x[10]来存放个位上数字分别为 1234567890个数
即个位是1个数存放在x[1]中个位是2个数存放在x[2]中……个位是0个数存放在 x[10]

void
{
a[101],x[11],i,p;

for(i=0;i<=11;i)
x[i]=0;

for(i=1;i<=100;i)
{
a[i]=rand % 100;
prf("%4d",a[i]);
(i%100)prf("\n");
}

for(i=1;i<=100;i)
{
p=a[i]%10;
(p0) p=10;
x[p]=x[p]+1;
}

for(i=1;i<=10;i)
{
p=i;
(i10) p=0;
prf("%d,%d\n",p,x[i]);
}
prf("\n");
}

2、求两个整数最大公约数、最小公倍数

分析:求最大公约数算法思想:(最小公倍数=两个整数的积/最大公约数)
(1) 对于已知两数mn使得m>n;
(2) m除以n得余数r;
(3) 若r=0则n为求得最大公约数算法结束;否则执行(4);
(4) m ← nn ← r再重复执行(2)
例如: 求 m=14 ,n=6 最大公约数. m n r
14 6 2
6 2 0
void
{
nm,r,n,m,t;
prf("please input two numbers:\n");
scanf("%d,%d",&m,&n);
nm=n*m;
(m<n)
{
t=n;
n=m;
m=t;
}

r=m%n;
while (r!=0)
{
m=n;
n=r;
r=m%n;
}
prf("最大公约数:%d\n",n);
prf("最小公倍数:%d\n",nm/n);
}

3、判断素数

  只能被1或本身整除数称为素数 基本思想:把m作为被除数
  将2至sqrt(m)作为除数如果都除不尽m就是素数否则就不是(可用以下段实现)
  
void
{
m,i,k;
prf("please input a number:\n");
scanf("%d",&m);
k=sqrt(m);
for(i=2;i<k;i)
(m%i0) ;
(i>=k)
prf("该数是素数");

prf("该数不是素数");
}

将其写成,若为素数返回1不是则返回0
prime( m)
{
i,k;
k=sqrt(m);
for(i=2;i<k;i)
(m%i0) 0;
1;
}

4、验证哥德巴赫猜想
(任意个大于等于6偶数都可以分解为两个素数的和)
基本思想:n为大于等于6偶数可分解为n1和n2两个数分别检查
n1和n2是否为素数如都是则为组解如n1不是素数就不必再检查n2是否素数
先从n1=3开始检验n1和n2(n2=N-n1)是否素数然后使n1+2 再检验n1、n2是否素数
直到n1=n/2为止

  利用上面prime验证哥德巴赫猜想代码如下:
# "math.h"
prime( m)
{
i,k;
k=sqrt(m);
for(i=2;i<k;i)
(m%i0) ;
(i>=k)
1;

0;
}


{
x,i;
prf("please input a even number(>=6):\n");
scanf("%d",&x);
(x<6||x%2!=0)
prf("data error!\n");

for(i=2;i<=x/2;i)
(prime(i)&&prime(x-i))
{
prf("%d+%d\n",i,x-i);
prf("验证成功!");
;
}
}

5、排序问题

1.选择法排序(升序)
基本思想:
1)对有n个数序列(存放在a(n)中)从中选出最小和第1个数交换位置;
2)除第1 个数外其余n-1个数中选最小和第2个数交换位置;
3)依次类推选择了n-1次后这个数列已按升序排列

代码如下:
void
{
i,j,imin,s,a[10];
prf("\n input 10 numbers:\n");
for(i=0;i<10;i)
scanf("%d",&a[i]);

for(i=0;i<9;i)
{
imin=i;
for(j=i+1;j<10;j)
(a[imin]>a[j]) imin=j;

(i!=imin)
{
s=a[i];
a[i]=a[imin];
a[imin]=s; }
prf("%d\n",a[i]);
}
}
}

2.冒泡法排序(升序)

基本思想:(将相邻两个数比较调到前头)
1)有n个数(存放在a(n)中)趟将每相邻两个数比较
调到前头经n-1次两两相邻比较后最大数已“沉底”
放在最后个位置小数上升“浮起”;
2)第 2趟对余下n-1个数(最大数已“沉底”)按上法比较
经n-2次两两相邻比较后得次大数;
3)依次类推n个数共进行n-1趟比较在第j趟中要进行n-j次两两比较
段如下

void
{
a[10];
i,j,t;
prf("input 10 numbers\n");
for(i=0;i<10;i)
scanf("%d",&a[i]);

prf("\n");
for(j=0;j<=8;j)
for(i=0;i<9-j;i)
(a[i]>a[i+1])
{
t=a[i];
a[i]=a[i+1];
a[i+1]=t;
}

prf("the sorted numbers:\n");

for(i=0;i<10;i)
prf("%d\n",a[i]);
}

3.合并法排序(将两个有序A、B合并成另个有序C升序)
基本思想:
1)先在A、B中各取第个元素进行比较将小元素放入C
2)取小元素所在个元素和另中上次比较后较大元素比较
重复上述比较过程直到某个被先排完;
3)将另剩余元素抄入C合并排序完成

段如下:
void
{
a[10],b[10],c[20],i,ia,ib,ic;
prf("please input the first .gif' />:\n");
for(i=0;i<10;i)
scanf("%d",&a[i]);

for(i=0;i<10;i)
scanf("%d",&b[i]);

prf("\n");
ia=0;ib=0;ic=0;
while(ia<10&&ib<10)
{
(a[ia]<b[ib])
{
c[ic]=a[ia];
ia;
}

{
c[ic]=b[ib];
ib;
}
ic;
}

while(ia<=9)
{
c[ic]=a[ia];
ia;ic;
}

while(ib<=9)
{
c[ic]=b[ib];
b;ic;
}

for(i=0;i<20;i)
prf("%d\n",c[i]);
}

6、查找问题
1.①顺序查找法(在列数中查找某数x)

基本思想:列数放在a[1]---a[n]中待查找数放在x 中把x和a元素
  从头到尾进行比较查找用变量p表示a元素下标p初值为1使x和a[p]比较
  如果x不等于a[p]则使 p=p+1不断重复这个过程;旦x等于a[p]则退出循环;
  另外如果p大于长度循环也应该停止(这个过程可由下语句实现)
  
void
{
a[10],p,x,i;
prf("please input the .gif' />:\n");
for(i=0;i<10;i)
scanf("%d",&a[i]);

prf("please input the number you want find:\n");
scanf("%d",&x);
prf("\n");
p=0;
while(x!=a[p]&&p<10)
p;
(p>=10)
prf("the number is not found!\n");

prf("the number is found the no%d!\n",p);
}
研究:将上面改写查找Find若找到则返回下标值找不到返回-1

②基本思想:列数放在a[1]---a[n]中待查找关键值为key
把key和a元素从头到尾进行比较查找若相同查找成功
若找不到则查找失败(查找子过程如下index:存放找到元素下标)

void
{
a[10],index,x,i;
prf("please input the .gif' />:\n");
for(i=0;i<10;i)
scanf("%d",&a[i]);

prf("please input the number you want find:\n");
scanf("%d",&x);
prf("\n");
index=-1;
for(i=0;i<10;i)
(xa[i])
{
index=i;
;
}

(index-1)
prf("the number is not found!\n");

prf("the number is found the no%d!\n",index);
}

2.折半查找法(只能对有序数列进行查找)

基本思想:设n个有序数(从小到大)存放在a[1]----a[n]中
要查找数为x用变量bot、top、mid 分别表示查找数据范围底部(下界)
顶部(上界)和中间mid=(top+bot)/2折半查找算法如下:
(1)x=a(mid)则已找到退出循环否则进行下面判断;
(2)x<a(mid)x必定落在bot和mid-1范围的内即top=mid-1;
(3)x>a(mid)x必定落在mid+1和top范围的内即bot=mid+1;
(4)在确定了新查找范围后重复进行以上比较直到找到或者bot<=top

将上面算法写成如下:

void
{
a[10],mid,bot,top,x,i,find;
prf("please input the .gif' />:\n");
for(i=0;i<10;i)
scanf("%d",&a[i]);

prf("please input the number you want find:\n");

scanf("%d",&x);
prf("\n");
bot=0;top=9;find=0;

while(bot<top&&find0)
{
mid=(top+bot)/2;
(xa[mid])
{
find=1;
;
}

(x<a[mid])
top=mid-1;

bot=mid+1;
}

(find1)
prf("the number is found the no%d!\n",mid);

prf("the number is not found!\n");
}

   7、插入法

  把个数插到有序数列中插入后数列仍然有序

  基本思想:n个有序数(从小到大)存放在a(1)—a(n)中要插入数x首先确定x插在位置P;(可由以下语句实现)
# N 10
void insert( a, x)
{ p, i;
p=0;
while(x>a[p]&&p<N)
p;
for(i=N; i>p; i--)
a[i]=a[i-1];
a[p]=x;
}

{ a[N+1]={1,3,4,7,8,11,13,18,56,78}, x, i;
for(i=0; i<N; i) prf("%d,", a[i]);
prf("\nInput x:");
scanf("%d", &x);
insert(a, x);
for(i=0; i<=N; i) prf("%d,", a[i]);
prf("\n");
}

   8、矩阵( 2维)运算

(1)矩阵加、减运算
C(i,j)=a(i,j)+b(i,j) 加法
C(i,j)=a(i,j)-b(i,j) 减法
(2)矩阵相乘
(矩阵A有M*L个元素矩阵B有L*N个元素则矩阵C=A*B有M*N个元素)矩阵C中任元素 (i=1,2,…,m; j=1,2,…,n)
# M 2
# L 4
# N 3
void mv( a[M][L], b[L][N], c[M][N])
{ i, j, k;
for(i=0; i<M; i)
for(j=0; j<N; j)
{ c[i][j]=0;
for(k=0; k<L; k)
c[i][j]a[i][k]*b[k][j];
}
}

{ a[M][L]={{1,2,3,4},{1,1,1,1}};
b[L][N]={{1,1,1},{1,2,1},{2,2,1},{2,3,1}}, c[M][N];
i, j;
mv(a,b,c);
for(i=0; i<M; i)
{ for(j=0; j<N; j)
prf("%4d", c[i][j]);
prf("\n");
}
}
(3)矩阵传置
例:有 2维a(5,5)要对它实现转置可用下面两种方式:
# N 3
void ch1( a[N][N])
{ i, j, t;
for(i=0; i<N; i)
for(j=i+1; j<N; j)
{ t=a[i][j];
a[i][j]=a[j][i];
a[j][i]=t;
}
}
void ch2( a[N][N])
{ i, j, t;
for(i=1; i<N; i)
for(j= 0; j<i; j)
{ t=a[i][j];
a[i][j]=a[j][i];
a[j][i]=t;
}
}

{ a[N][N]={{1,2,3},{4,5,6},{7,8,9}}, i, j;
ch1(a); /*或ch2(a);*/
for(i=0; i<N; i)
{ for(j=0; j<N; j)
prf("%4d", a[i][j]);
prf("\n");
}
}
(4)求 2维中最小元素及其所在行和列
基本思路同可用下面段实现(以 2维a[3][4]为例):
‘变量max中存放最大值row,column存放最大值所在行列号
# N 4
# M 3
void min( a[M][N])
{ min, row, column, i, j;
min=a[0][0];
row=0;
column=0;
for(i=0; i<M; i)
for(j=0; j<N; j)
(a[i][j]<min)
{ min=a[i][j];
row=i;
column=j;
}
prf("Min=%d\nAt Row%d,Column%d\n", min, row, column);
}

{ a[M][N]={{1,23,45,-5},{5,6,-7,6},{0,33,8,15}};
min(a);
}

   9、迭代法

  算法思想:对于个问题求解x可由给定个初值x0根据某迭代公式得到个新值x1这个新值x1比初值x0更接近要求值x;再以新值作为初值即:x1→x0,重新按原来思路方法求x1,重复这过和直到|x1-x0|<ε(某给定精度)此时可将x1作为问题
例:用迭代法求某个数平方根 已知求平方根迭代公式为:
#<math.h>
float fsqrt(float a)
{ float x0, x1;
x1=a/2;
do{
x0=x1;
x1=0.5*(x0+a/x0);
}while(fabs(x1-x0)>0.00001);
(x1);
}

{ float a;
scanf("%f", &a);
prf("genhao =%f\n", fsqrt(a));
}

  十、数制转换

  将个十进制整数m转换成 →r(2-16)进制

  思路方法:将m不断除 r 取余数直到商为零以反序得到结果下面写出转换参数idec为十进制数ibase为要转换成数基(如 2进制基是2 8进制基是8等)输出结果是
char *trdec( idec, ibase)
{ char strdr[20], t;
i, idr, p=0;
while(idec!=0)
{ idr=idec % ibase;
(idr>=10)
strdr[p]=idr-10+65;

strdr[p]=idr+48;
idec/=ibase;
}
for(i=0; i<p/2; i)
{ t=strdr[i];
strdr[i]=strdr[p-i-1];
strdr[p-i-1]=t;
}
strdr[p]=’\0’;
(strdr);
}

{ x, d;
scanf("%d%d", &x, &d);
prf("%s\n", trdec(x,d));
}

  十般处理

  1.简单加密和解密
加密思想是: 将每个字母C加(或减)序数K即用它后第K个字母代替变换式公式: c=c+k
例如序数k为5这时 A→ F a→fB→?G… 当加序数后字母超过Z或z则 c=c+k -26
例如:You are good→ Dtz fwj ltti
解密为加密逆过程
将每个字母C减(或加)序数K即 c=c-k,
例如序数k为5这时 Z→Uz→uY→T… 当加序数后字母小于A或a则 c=c-k +26
下段是加密处理:
#<stdio.h>
char *jiami(char stri)
{ i=0;
char strp[50],ia;
while(stri[i]!=’\0’)
{ (stri[i]>=’A’&&stri[i]<=’Z’)
{ ia=stri[i]+5;
(ia>’Z’) ia-=26;
}
(stri[i]>=’a’&&stri[i]<=’z’)
{ ia=stri[i]+5;
(ia>’z’) ia-=26;
}
ia=stri[i];
strp[i]=ia;
}
strp[i]=’\0’;
(strp);
}

{ char s[50];
gets(s);
prf("%s\n", jiami(s));
}
2.统计文本单词个数
输入统计其中有多少个单词单词的间用格分隔开
算法思路:
(1)从文本(串)左边开始取出;设逻辑量word表示所取是否是单词内初值设为0
(2)若所取不是“空格”“逗号”“分号”或“感叹号”等单词分隔符再判断word是否为1若word不为1则表是新单词开始让单词数num = num +1让word =1;
(3)若所取是“空格”“逗号”“分号”或“感叹号”等单词分隔符 则表示不是单词内让word=0;
(4) 再依次取下重得(2)(3)直到文本结束
下面段是中包含单词数
# "stdio.h"

{char c,[80];
i,num=0,word=0;
gets();
for(i=0;(c=[i])!='\0';i)
(c' ') word=0;
(word0)
{ word=1;
num;}
prf("There are %d word in the line.\n",num);
}

  十 2、穷举法
  
  穷举法(又称“枚举法”)基本思想是:列举各种可能情况并判断哪种可能是符合要求这是种“在没有其它办法情况思路方法”种最“笨”思路方法然而对些无法用解析法求解问题往往能奏效通常采用循环来处理穷举问题
例: 将张面值为100元人民币等值换成100张5元、1元和0.5元零钞要求每种零钞不少于1张问有哪几种组合?

{ i, j, k;
prf(" 5元 1元 5角\n");
for(i=1; i<=20; i)
for(j=1; j<=100-i; j)
{ k=100-i-j;
(5*i+1*j+0.5*k100)
prf(" %3d %3d %3d\n", i, j, k);
}
}

  十 3、递归算法
  
  用自身结构来描述自身称递归
  
  VB允许在个Sub子过程和Function过程定义内部自己即递归Sub子过程和递归Function递归处理般用栈来实现次自身把当前参数压栈直到递归结束条件;然后从栈中弹出当前参数直到栈空
递归条件:(1)递归结束条件及结束时值;(2)能用递归形式表示且递归向终止条件发展
例:编fac(n)=n! 递归
fac( n)
{ (n1)
(1);

(n*fac(n-1));
}

{ n;
scanf("%d", &n);
prf("n!=%d\n", fac(n));
}

*/

/*
签到工作后用人单位承诺是嵌入式方向让我好生欢喜而且用人单位也很负责任为我们应届毕业生着想委托了家在东北地区小有名气培训机构为我们提供价格不菲培训 其中当然少不了C语言于是特意买了本C语言科学和艺术好好复习C
个博士生讲解C语言中级教程虽然没有多少新意但是总有些久而荒废知识贴出来不断提醒自己“温故知新”


1.有关prf输出
自从学过C就再也没有用过Cprf和std::cout相比prf不能判断输出量类型而且对内存操作会有危险



prf原型中参数是常量串+...;返回值是整形

其中常量串中输出格式需要注意:常用%d、%f、%s不用多说但是%g和%%需要介绍说明下:
%g表示普通型数值用%f和%e格式中较短个显示在不能事先确定输出值情况下他是最好输出格式如定义了float型变量如果它是个整数值那么以%g格式输出不会显示堆0好像是以%d格式输出若是小数值也不会补充堆0让人看得很亲切
%%是百分号输出表示而不是我们常规思路那样用转义\

精度控制:
负号:表示数值左对齐没有负号右对齐;
宽度:表示输出字段最小若要显示数值所占空间少则以空格 填充;若数值太大不能在指定大小字段中显示那么扩大字段宽度直到能够容纳这个数值
小数点精度:对%g而言精度参数介绍说明了最大有效位;%f和%e来说精度参数指定了小数点后位数;%s而言则表示串中显示位数
注意:小数点占位!
返回值为在windows下平台下表示了prf输出个数或者发生输出相关字节特别注意点:\t\n等制表换行虽然在输出是没有确切显示但是也算!




2.有关scanf
这个样没法识别输入量类型所以也要在参数表里显示指定类型如%d,%f等等但是有点区别于prf就是%f,在输出时%f可以输出double和float两种类型但是输入时double位数明显大于float于是如果象这样:
double d;
scanf(“%f”,&d);
prf(“%f”,d);
就会输出%!$%#^@#%堆不知所云东西原因就在于类型宽度问题正确是以%lf输出

在windows32位系统环境下 2; 4;float 4;double 8;但是让我不理解是long double也是8(如何也应该比double大呀);
在linux32位系统环境下后只有long double于win32区别是12这让我少感欣慰
但是还有点需要注意:输入时可加上宽度限制但是不可加精度限制;
如:char c,d;scanf(“%3c,%3c”,&c,&d);prf(“%c,%d”,c,d);若输入abcdef[Enter]则会认定abc中赋给c是adef中赋给d是d



3.getchar和putchar

这两个都是次处理以前曾写过:
while(c = getchar)
{putchar(c);
putchar('\n');...}
这次和班里位同学讨论说按照常理研究应该是输入马上输出但当我们输入串时会把这个串挨个输出

由于这两个都可由以上两个实现所以我想是不是putchar中用到了scanf然后向2那样把输入串放到个buffer里然后依次截取下来轮流处理
而若想输入马上输出可能是需要系统检测keydown中断来处理
这个问题还不明白希望如果有哪位高手路过指点下!谢谢




有个哲人曾说过“知半解是危险”最近对这句话体会越来越深刻


林语堂先生说过“只用样东西不明白它道理是在不高明”谨记慎行!


全局变量&局部变量
当全局变量和局部变量同名时会遮蔽全局变量内引用此变量时会用到同名局部变量而不会用到全局变量(如果想引用全局变量需要加上::)而且局部变量可以相互遮蔽嵌套for嵌套等

这都和局部变量作用域有关而作用域又根据各编译器实现有关这就联系到编译原理内容(顺便复习下)
般而言标示符符号表要包含几种属性:名字、类型、存储类别、作用域、可视性、存储分配信息
1)名字:
符号表中符号名般不允许重名出现重名标示符则根据语言定义 按照该标示符作用域和可视性规则进行相应处理
允许重载名通过它们参数个数、类型、返回值来区分
2)类型:
决定变量数据在存储空间存储格式还决定了在变量上可施加运算
3)存储类别:
其定义思路方法有两种形式:关键字指定变量定义+位置
这个属性是编译过程语义处理检查和存储分配重要依据还决定了它作用 域可视性生命周期等问题
4)作用域和可视性:
通常个变量作用域就是该变量可以出现地方;形参作为内部变量处理;分结构本身含有局部变量声明语句
5)存储分配:
编译般根据标示符存储类别以及他们出现位置和次序来确定每个变量应分配存储区及该区域具体位置
静态存储区 公共静态区 公共+外部 整个域作用
局部静态区 局部静态 局部
动态存储区
标示符在源中出现位置和先后顺序决定了标示符在存储区中具体位置个偏移量

有关符号表建立:
:

{ a = 1; float c =0.1;
{
float a =1.0;
{float x=5.5,b =7.1;}
{ b =9; c =a+b+c;}
}
}
1.建立符号表第层到内存中指向;
2.建立符号表第 2层到内存中ac指向指向a;
3.建立符号表第 3层到内存中a指向指向a;(第 2层a和第 3层a区别)
4.建立符号表第 4层到内存中xb指向遇到后个括号}表明本层结束建立内存中结束项
然后从结束项建立 指针指向上层最后个元素即第 3层a然后删除本层符号表到内
存中指针;
......
n.最后形成结果是符号表到内存中共有 3个指向分别指向、a和ca、a然后又很多
结束项指回前 3层
(由于不会在blog里用图形所以不能更形象地标示出来等以后整明白)

特别点:
如果是区别C文件中方式来声明同名全局变量如果要正常运行要求只能有个C文件对此变量赋值此时连接不会出错



补充:

Static全局变量和普通全局变量区别

全局变量本身就是静态存储方式静态全局变量当然也是两者在存储方式上并无区别
这两者区别在于非静态全局变量作用域是整个源个源由多个源文件构成时非静态全局
变量在各个文件中都是有效而静态全局变量则限制了其作用域即只在定义该变量源文件内有效在同
其他源文件中不可使用
由于静态全局变量作用域限制于个源文件中只能为该文件内使用也可避免些在其他文件中
引起

Static局部变量和普通局部变量区别
静态局部变量存储方式和普通局部变量区别因此生命周期也区别后者是动态存放于堆栈中
全局在静态区动态分配在堆中

Static和普通区别
Static在内存中只有份拷贝而普通每次都会有份拷贝

*/
/*

"C的诡谲"C语言的精华整理总结!
C的诡谲(上)
从研究生 2年纪开始学习计算机也差不多两年了路走来有很多收获也有不少遗憾现在正好有段闲暇就想对走过路留下些足迹回忆每个人都有自己区别人生说到这里就是人生了歌德在浮士德中说过:“如果不曾在悲哀中咀嚼过面包不曾在哭泣中等待过明天这样人就不知道你——天力量”所以我想记下些带给我悲哀带给我哭泣人生其实学习计算机基础课程是非常重要离散数学编译原理操作系统形式语言……如果你认真走过了这些路在以后日子你会发现你路会越走越宽以前努力和汗水会不断给你灵感给你支持给你前进武器和勇气你会发现以后取得很多成就不过是朝花夕拾而已!

对于语言我喜欢是C它能带给你别语言无法给予你无上智力快感当然也会给你门语言所能给你魔鬼般折磨其实JavaC#Python语言也非常不错我也极为喜欢它们都是非常成功语言我从来就不愿意做某种语言盲目信仰者每种语言都有它成功地方失败地方都有它适合地方不如意地方所以每次看到评价语言文章我看看但从来不会发言

C前世是C而且C所留下神秘以及精简在C中是青出于蓝而胜于蓝!C所带给人困惑以及灵活太多即使个有几年经验高段C员仍然有可能在C语言小水沟里翻船不过其实C语言真不难下面我想指出C语言中最神秘而又诡谲多变 4个地方它们也继续在C语言中变幻莫测

指针类型识别参数可变

.指针

本质是地址类型在许多语言中根本就没有这个概念但是它却正是C灵活高效在面向过程时代所向披靡原因所在C内存模型基本上对应了现在von Neumann(冯·诺伊曼)计算机机器模型很好达到了对机器映射不过有些人似乎永远也不能理解指针【注1】

注1:Joel Spolsky就是这样认为他认为对指针理解是种aptitude不是通过训练就可以达到http://www.joelonsoftware.com/pr ... /fog0000000073.html

指针可以指向值、当然它也可以作为值使用

看下面几个例子:

* p;//p是个指针指向个整数

** p;//p是个指针它指向第 2个指针然后指向个整数

(*pa)[3];//pa是个指针指向个拥有3个整数

(*pf);//pf是个指向指针这个返回个整数

后面第 4节我会详细讲解标识符(identier)类型识别

1.指针本身类型是什么?

先看下面例子: a;//a类型是什么?

把a去掉就可以了因此上面4个声明语句中指针本身类型为:

*

**

(*)[3]

(*)

它们都是复合类型也就是类型和类型结合而成类型意义分别如下:

po to (指向个整数指针)

poer to poer to (指向个指向整数指针指针)

poer to .gif' /> of 3 s(指向个拥有 3个整数指针)

poer to function of parameter is void and value is (指向指针这个参数为空返回值为整数)

2.指针所指物类型是什么?

很简单指针本身类型去掉 “*”号就可以了分别如下:



*

[3]



3和4有点怪不是吗?请擦亮你眼睛在那个用来把“*”号包住”是多余所以:

[3]就是 [3](个拥有 3个整数)

就是 (参数为空返回值为整数)【注2】

注2:个小小提醒第 2个“”是个运算符名字叫运算符(function call operator)

3.指针算术运算

请再次记住:指针不是个简单类型它是个和指针所指物类型复合类型因此算术运算和的(指针所指物类型)密切相关

a[8];

* p = a;

* q = p + 3;

p;

指针加减并不是指针本身 2进制表示加减要记住指针是个元素地址它每加就指向下个元素所以:

* q = p + 3;//q指向从p开始第 3个整数

p;//p指向下个整数

double* pd;

……//某些计算的后

double* pother = pd – 2;//pother指向从pd倒数第 2个double数

4.指针本身大小

个现代典型32位机器上【注3】机器内存模型大概是这样想象内存空间就像个连续房间群个房间大小是个字节(般是 2进制8位)有些东西大小是个字节(比如char)个房间就把它给安置了;但有些东西大小是几个字节(比如double就是8个字节就是4个字节我说是典型32位)所以它就需要几个房间才能安置

注3:什么叫32位?就是机器CPU次处理数据宽度是32位机器寄存器容量是32位机器数据内存地址总线是32位当然还有些细节但大致就是这样16位64位128位可以以此类推

这些房间都应该有编号(也就是地址)32位机器内存地址空间当然也是32位所以房间个编号都用32位 2进制数来编码【注4】请记住指针也可以作为值使用作为值时候它也必须被安置在房间中(存储在内存中)那么指向个值指针需要个地址大小来存储即32位4个字节4个房间来存储

注4:在我们平常用到32位机器上绝少有将32位真实内存地址空间全用完(232 = 4G)即使是服务器也不例外现代操作系统般会实现32位虚拟地址空间这样可以方便运用编制有关虚拟地址(线性地址)和真实地址区别以及实现可以参考Linux源代码情景分析第 2章存储管理在互联网上有关这个主题文章汗牛充栋你也可以google

但请注意在C中指向对象成员指针(poer to member data or member function)大小不定是4个字节为此我专门编制了发现在我两个编译器(VC7.1.3088和Dev-C4.9.7.0)上指向对象成员指针大小没有定值但都是4倍数区别编译器还有区别对于普通类()指向对象成员指针大小般为4但在引入多重虚拟继承以及虚拟时候指向对象成员指针会增大不论是指向成员数据还是成员【注5】

注5:在Andrei AlexandrescuModern C Design5.13节Page124中提到成员指针实际上是带标记(tagged)unions它们可以对付多重虚拟继承以及虚拟书上说成员指针大小是16但我实战告诉我这个结果不对而且具体编译器实现也区别直很想看看GCC源代码但由于旁骛太多而且心不静本身难度也比较高(这个倒是不害怕^_^)只有留待以后了

还有个类 member来说指向它指针只是普通指针不是poer to member所以它大小是4

5.指针运算符&和*

它们是对相反操作&取得个东西地址(也就是指针)*得到个地址里放东西这个东西可以是值(对象)、、类成员( member)

其实很简单房间里面居住着个人&操作只能针对人取得房间号码;

*操作只能针对房间取得房间里

参照指针本身类型以及指针所指物类型很好理解

小结:其实你只要真正理解了12就相当于掌握了指针牛鼻子后面就不难了指针各种变化和C语言中其它普通类型变化都差不多(比如各种转型)

2.

在C语言中对于你只需要理解 3件事

1.C语言中有且只有

所谓n维只是个称呼种方便记法都是使用来仿真

C语言中元素可以是任何类型东西特别作为元素也可以所以 a[3][4][5]就应该这样理解:a是个拥有3个元素其中每个元素是个拥有4个元素步其中每个元素是拥有5个整数元素

是不是很简单!a内存模型你应该很容易就想出来了不是吗?:)

2.元素个数必须作为整数常量在编译阶段就求出来

i;

a;//不合法编译不会通过

也许有人会奇怪char str = “test”;没有指定元素个数为什么也能通过编译器可以根据后面串在编译阶段求出来

不信你试试这个: a;

编译器无法推断所以会判错说“.gif' /> size missing in a”的类信息不过在最新C99标准中实现了变长【注6】

注6:如果你是个好奇心很强烈就像我那么可以查看C99标准6.7.5.2

3.对于可以获得个(即下标为0)元素地址(也就是指针)名获得

比如 a[5]; * p = a;这里p就得到了元素a[0]地址

其余对于各种操作其实都是对于指针相应操作比如a[3]其实就是*(a+3)简单写法由于*(a+3)*(3+a)所以在某些代码中你会看到类似3[a]这种奇怪表达式现在你知道了它就是a[3]别名还有种奇怪表达式类似a[-1]现在你也明白了它就是*(a-1)【注7】

注7:你肯定是个很负责任而且也知道自己到底在干什么你难道不是吗?:)所以你定也知道件事是要付出成本当然也应该获得多于成本回报

我很喜欢经济学经济学个基础就是做什么事情都是要花成本即使你什么事情也不做时间成本金钱成本机会成本健康成本……可以这样说经济学根本目就是用最小成本获得最大回报

所以我们在自己中最好避免这种邪恶写法不要让自己智力过剩带来以后自己和他人长时间痛苦用韦小宝句话来说:“赔本生意老子是不干!”

但是对邪恶了解是非常必要这样当我们真正遇到邪恶时候可以免受它对心灵困扰!

对于指向同区别元素指针它们可以做减法比如* p = q+i;p-q结果就是这两个指针的间元素个数i可以是负数但是请记住:对指向区别元素指针这样做法是无用而且邪恶

对于所谓n维比如 a[2][3];你可以得到个元素地址a和它大小*(a+0)(也即a[0]或者*a)就是第个元素它又是[3]继续取得它个元素*(*(a+0)+0)(也即a[0][0]或者*(*a))也即第个整数(第行第个整数)如果采用这种表达式就非常笨拙所以a[0][0]记法上简便就非常有用了!简单明了!

对于你只能取用在有效范围内元素和元素地址不过最后个元素个元素地址是个例外它可以被用来方便各种计算特别是比较运算但显然它所指向内容是不能拿来使用和改变

有关本身大概就这么多下面简要说和指针关系它们关系非常暧昧有时候可以交替使用

比如 ( args, char* argv)中其实参数列表中char* argv就是char** argv种写法在C语言中是不能作为引数(argument)【注8】直接传递那样非常损失效率而这点违背了C语言设计时基本理念——作为门高效系统设计语言

注8:这里我没有使用实参这个大陆术语而是运用了台湾术语它们都是argument这个英文术语翻译但在很多地方中文实参用并不恰当非常勉强而引数表示被引用很形象也很好理解很快你就可以像我样适应引数而不是实参

dereferance也就是*运算符操作我也用是提领而不是解引用

我认为你定智勇双全:既有宽容智慧也有面对新事物勇气!你不愿意承认吗?:)

所以在参数列表(parameter list)中形式参数声明只是为了方便阅读!比如上面char* argv就可以很容易想到是对个char*进行操作其实质是传递char*首元素地址(指针)其它元素当然可以由这个指针加法间接提领(dereferance)【参考注8】得到!从而也就间接得到了整个

但是和指针还是有区别比如在个文件中有下面定义:

char myname = “wuaihua”;

而在另个文件中有下列声明:

extern char* myname;

它们互相是并不认识尽管你本义是这样希望

它们对内存空间使用方式区别【注9】

对于char myname = “wuaihua”如下

myname

w
u
a
i
h
u
a
\0


对于char* myname;如下表

myname




\|/
w
u
a
i
h
u
a
\0


注9:可以参考Andrew KonigC陷阱和缺陷4.5节

改变思路方法就是使它们致就可以了

char myname = “wuaihua”;

extern char myname;

或者

char* myname = “wuaihua”;//C中最好换成const char* myname = “wuaihua”

extern char* myname;

C的诡谲(下)
3.类型识别

基本类型识别非常简单:

a;//a类型是a

char* p;//p类型是char*

……

那么请你看看下面几个:

* (*a[5])(, char*); //#1

void (*b[10]) (void (*)); //#2

doube(*) (*pa)[9]; //#3

如果你是第次看到这种类型声明时候我想肯定跟我感觉就如晴天霹雳 5雷轰顶头昏目眩头张牙舞爪狰狞怪兽扑面而来

不要紧(Take it easy)!我们慢慢来收拾这几个面目可憎纸老虎!

1.C语言中声明和声明

声明般是这样 fun(,double);对应指针(poer to function)声明是这样:

(*pf)(,double)你必须习惯可以这样使用:

pf = &fun;//赋值(assignment)操作

(*pf)(5, 8.9);//操作

也请注意C语言本身提供了种简写方式如下:

pf = fun;// 赋值(assignment)操作

pf(5, 8.9);// 操作

不过我本人不是很喜欢这种简写它对初学者带来了比较多迷惑

声明般是这样 a[5];对于指针(poer to .gif' />)声明是这样:

(*pa)[5]; 你也必须习惯可以这样使用:

pa = &a;// 赋值(assignment)操作

i = (*pa)[2]//将a[2]赋值给i;


2.有了上面基础我们就可以对付开头 3只纸老虎了!:)

这个时候你需要复习下各种运算符优先顺序和结合顺序了顺便找本书看看就够了

#1:* (*a[5])(, char*);

首先看到标识符名a”优先级大于“*”a和“[5]”先结合所以a是这个有5个元素个元素都是个指针指针指向“(, char*)”指向参数是“, char*”返回值是“*”完毕我们干掉了第个纸老虎:)

#2:void (*b[10]) (void (*));

b是这个有10个元素个元素都是个指针指针指向参数是“void (*)”【注10】返回值是“void”完毕!

注10:这个参数又是个指针指向参数为空返回值是“void”

#3. doube(*) (*pa)[9];

pa是个指针指针指向这个有9个元素个元素都是“doube(*)”【也即个指针指向参数为空返回值是“double”】

现在是不是觉得要认识它们是易如反掌工欲善其事必先利其器!我们对这种表达方式熟悉的后就可以用“typedef”来简化这种类型声明

#1:* (*a[5])(, char*);

typedef * (*PF)(, char*);//PF是个类型别名【注11】

PF a[5];//跟* (*a[5])(, char*);效果样!

注11:很多初学者只知道typedef char* pchar;但是对于typedef其它使用方法不太了解Stephen Blaha对typedef使用方法做过个整理总结:“建立个类型别名思路方法很简单在传统变量声明表达式里用类型名替代变量名然后把关键字typedef加在该语句开头”可以参看杂志2001.3期C高手窍门技巧20招

#2:void (*b[10]) (void (*));

typedef void (*pfv);

typedef void (*pf_taking_pfv)(pfv);

pf_taking_pfv b[10]; //跟void (*b[10]) (void (*));效果样!

#3. doube(*) (*pa)[9];

typedef double(*PF);

typedef PF (*PA)[9];

PA pa; //跟doube(*) (*pa)[9];效果样!


3.const和volatile在类型声明中位置

在这里我只说constvolatile是【注12】!

注12:顾名思义volatile修饰量就是很容易变化不稳定它可能被其它线程操作系统硬件等等在未知时间改变所以它被存储在内存中每次取用它时候都只能在内存中去读取它不能被编译器优化放在内部寄存器中

类型声明中const用来修饰个常量我们般这样使用:const在前面

const ;//是const

const char*;//char是const

char* const;//*(指针)是const

const char* const;//char和*都是const

对初学者const char*;和 char* const;是容易混淆这需要时间历练让你习惯它

上面声明有个对等写法:const在后面

const;//是const

char const*;//char是const

char* const;//*(指针)是const

char const* const;//char和*都是const

次你可能不会习惯但新事物如果是好我们为什么要拒绝它呢?:)const在后面有两个好处:

A. const所修饰类型是正好在它前面如果这个好处还不能让你动心那请看下个!

B.我们很多时候会用到typedef类型别名定义比如typedef char* pchar如果用const来修饰当const在前面时候就是const pchar你会以为它就是const char* 但是你错了真实含义是char* const是不是让你大吃惊!但如果你采用const在后面写法意义就如何也不会变不信你试试!

不过在真实项目中命名致性更重要你应该在两种情况下都能适应并能自如转换公司习惯商业利润不论在什么时候都应该优先考虑!不过在开始个新项目时候你可以考虑优先使用const在后面习惯使用方法


4.参数可变

C语言中有种很奇怪参数“…”它主要用在引数(argument)个数不定最常见就是prf

prf(“Enjoy yourself everyday!\n”);

prf(“The value is %d!\n”, value);

……

你想过它是如何实现吗?

1. prf为什么叫prf?

不管是看什么我总是个喜欢刨根问底对事物源有种特殊癖好段典故个成语句行话我最喜欢就是找到它来历和当时意境个外文翻译过来术语最低要求我会尽力去找到它原本外文术语特别是个字命名来历向是非常在意中国有句古话:“名不正则言不顺”prf中f就是format意思即按格式打印【注13】

注13:其实还有很多很多变量很多命名在各种语言中都是非常讲究你如果细心观察追溯定有很多乐趣和满足比如哈希表为什么叫hashtable而不叫hashlist?在CSGI STL实现中有个专门用于递增iota(不是itoa)为什么叫这个奇怪名字你想过吗?

看文章我不喜欢意犹未尽己所不欲勿施于人所以我把这两个答案告诉你:

(1)table和list做为表讲区别:

table:

-------|--------------------|-------

item1 | kadkglasgaldfgl | jkdsfh

-------|--------------------|-------

item2 | kjdszhahlka | xcvz

-------|--------------------|-------

list:

****

***

*******

*****

That's the dference!

如果你还是不明白可以去看下hash是如何实现

(2)The name iota is taken from the programming language APL.

而APL语言主要是做数学计算在数学中有很多公式会借用希腊字母

希腊字母表中有这样个字母大写为Ι小写为ι

英文拼写正好是iota这个字母在θ(theta)和κ(kappa)的间!

你可以http://www.wikipedia.org/wiki/APL_programming_language

下面有段是这样:

APL is renowned for using a of non-ASCII symbols that are an extension of traditional arithmetic and algebraic notation. These cryptic symbols, some have joked, make it possible to construct an entire air traffic control system in two lines of code. Because of its condensed nature and non-standard characters, APL has sometimes been termed a "write-only language", and reading an APL program can feel like decoding an alien tongue. Because of the unusual character-, many programmers used special APL keyboards in the production of APL code. Nowadays there are various ways to write APL code using _disibledevent=>
# va_end(list)

# va_arg(list, mode)
((mode*) (list (mode)))[-1]

注14:你可以查看C99标准7.15节获得详细而权威介绍说明也可以参考Andrew KonigC陷阱和缺陷附录A

ANSI C还提供了vprf它和对应prf行为方式上完全相同只不过用va_list替换了格式串后参数序列至于它是如何实现你在认真读完The C Programming Language我相信你定可以do it yourself!

使用这些工具我们就可以实现自己可变参数比如实现个系统化处理error它和prf使用差不多只不过将stream重新定向到stderr在这里我借鉴了C陷阱和缺陷附录A例子

实现如下:

#

#

void error(char* format, …)

{

va_list ap;

va_start(ap, format);

fprf(stderr, “error: “);

vfprf(stderr, format, ap);

va_end(ap);

fprf(stderr, “\n”);

exit(1);

}

你还可以自己实现prf:

#

prf(char* format, …)

{

va_list ap;

va_start(ap, format);

n = vprf(format, ap);

va_end(ap);

n;

}

我还专门找到了VC7.1头文件看了发现各个宏具体实现还是有区别跟很多预处理(preprocessor)相关其中va_list就不定是char*别名

typedef struct {

char *a0; /* poer to first homed eger argument */
off; /* off of next parameter */
} va_list;

其它定义类似


经常在Windows进行系统编程定知道有好几种区别形式比如__stdcall__pascal__cdecl在Windows下_stdcall__pascal是所以我只说下__stdcall和__cdecl区别

(1)__stdcall表示被端自身负责引数压栈和出栈参数个数都是这种形式

例如: fun(char c, double d)我们在中使用它这个就只管本身运行参数如何来如何去概不管自然有负责不过区别编译器实现可能将参数从右向左压栈也可能从左向右压栈这个顺序我们是不能加于利用【注15】

注15:你可以在Herb SutterMore Exceptional C条款20:An Unmanaged Poer Problem, Part 1:Parameter Evaluation找到相关细节论述

(2)__cdecl表示端负责被端引数压栈和出栈参数可变采用是这种形式

为什么这种要采用区别于前面形式呢?那是__stdcall形式对它没有作用端根本就无法知道引数个数它如何可能正确工作?所以这种方式是必须不过由于参数参数可变本身不多所以用地方比较少

对于这两种方式你可以编制些简单然后反汇编在汇编代码下面你就可以看到实际区别很好理解

重载有很多匹配(match)规则参数为“…”是匹配最低点在Andrei Alexandrescu惊才绝艳的作Modern C Design中就有用到参看Page34-352.7“编译期间侦测可转换性和继承性”


后记:

C语言细节肯定不会只有这么多但是这几个出现比较频繁而且在C语言中也是很重要几个语言特征如果把这几个细节彻底弄清楚了C语言本身神秘就不会太多了

C语言本身就像把异常锋利剪刀你可以用它做出非常精致优雅艺术品也可以剪出些乱 7 8糟废纸片能够将件武器用到出神入化那是需要时间需要多长时间?不多请你拿出万个小时来英国Exter大学心理学教授麦克.侯威专门研究神童和天才结论很有意思:“般人以为天才是自然而生、流畅而不受阻闪亮才华其实天才也必须耗费至少十年光阴来学习他们特殊技能绝无例外要成为专家需要拥有顽固个性和坚持能力……每专业人士都投注大量心血培养自己专业才能”【注16】

注16:台湾女作家、电视节目主持人吴淡如拿出万个小时来读者2003.1期“不用太努力只要持续下去想拥有辈子专长或兴趣就像个人跑马拉松赛最重要是跑完而不是前头跑得有多快

推荐两本书:

K&RThe C Programming languageSecond Edition

Andrew KonigC陷阱和缺陷本文从中引用了好几个例子本高段经验的谈

但是对纯粹初学者不太合适如果你有设计基础知识个月时间好好看看这两本书C语言本身就不用再花更多精力了
*/

/*
刚看完书说什么也得写点东西整理总结不自量力挑我认为最困难部分吧

给我印象最深刻就是两个字:“左值”当然还得加上两个字:“不是”名是个指针指针是左值但是名在C中不是个左值所以在C中不能出现在赋值运算左边
要牢记这所以不可以出现把名当整个赋值要赋值没什么好办法循环吧或者用些库里但是在用时候要牢记长度要提防越界像我们不经常使用fscanf这样有缓冲区就要顾及他溢出问题可以制定个字段宽度表示要读入最大

要确定元素个数可以用到如:(a)/(a[0])个元素大小去除整个大小

还有点需要注意是:值传递当用个简单变量会受到被参数个拷贝这就是参数传递而当参数形参和实参关系就发生了变化变成了值传递(不会在blog里插入图片-_-b)用语言来描述其原因就是指针作为参数传递给只有基址——首元素——名传递给作为局部栈地址这个名字存放了实际地址所以即便是想象“值赋值”(当然不能原因如上)内容也是如果选择在范围内定义局部个元素那么把偏移量+基地址样会在实际中操作最终结果是声明中定义形参和时使用实参是相同块内存而不是个拷贝


指针
无疑指针是C中最精髓部分指针可以在化后可以同时拥有所指变量两样东西——值和地址这就给我们写时很大空间可以直接和内存对话!这也同样引出了千奇百怪不知道该如何表达其实最根本是要明白我们在使用指针时候知道我们使用究竟是她哪个性质!是值?还是地址?于此对应指针有两种最基本操作:
个是取地址&主要用于化时赋值操作&必须是左值
个是取指向值**可以取任意指向返回其左值
对指针操作犹如打太极有很多招式但又归于最基础是分清指针赋值和值赋值:
p1=p2;指针赋值是p1和p2指向同位置
*p1=*p2;值赋值把p2为地址内存中内容赋到p1为地址内存
注意:指针也是有地址它本身也需要在内存中开辟块存储这块存储空间里是他所指变量地址然后根据这个地址可以找到所指变量值!
指针可以被运算但要注意是指针所指向对象类型指针都是——4而他指向解析方式是区别所以同样形式会有区别运算思路方法如:p对于型和对于double型所跨越实际地址是区别


指针和
我们使用指针时候其作用和其他变量相似可以把他行为和基本类型划等号但是如果是就区别了声明后保有很多内存单元每个元素都有个内存单元名不和某个单独内存单元相对应而是和整个内存单元集合相对应所以这点和普通变量区别
当变量做最普通声明时会体现和指针最关键区别: .gif' />【5】;和 * p;内存分配!这样指针是不分配内存 但是分配!
*/

/*
、文件包含
# <头文件名称>
# "头文件名称"
种形式 : 用来包含开发环境提供库头文件它指示编译预处理器在开发环境设定搜索路径中查找所需头文件
第 2种形式 : 用来包含自己编写头文件它指示编译预处理器首先在当前工作目录下搜索头文件如果找不到再到开发环境设定路径中查找

内部包含卫哨和外部包含卫哨
在头文件里面使用内部包含卫哨就是使用种标志宏可以放心在同个编译单元及其包含头文件中多次包含同个头文件而不会造成重复包含如:
#ndef _STDDEF_H_INCLUDED_
# _STDDEF_H_INCLUDED_
...... //头文件内容
#end

当包含个头文件时候如果能够始终如地使用外部包含卫哨可以显著地提高编译速度个头文件被个源文件反复包含多次时可以避免多次查找和打开头文件地操作如:
# !d(_INCLUDED_STDDEF_H_)
# <stddef.h>
# _INCLUDED_STDDEF_H_
#end
建议外部包含卫哨和内部包含卫哨使用同个标志宏这样可以少定义个标志宏如:
# !d_STDDEF_H_INCLUDED_
# <stddef.h>
#end

头文件包含合理顺序
在头文件中:
1、包含当前工程中所需自定义头文件
2、包含第 3方头文件
3、包含标准头文件
在源文件中:
1、包含该源文件对应头文件
2、包含当前工程中所需自定义头文件
3、包含第 3方头文件
4、包含标准头文件




避免重定义
  如果把个struct定义放在个头文件中就有可能在个编译中多次包含这个头文件编译器认为重定义是如下面例子:
  // file : type.h
  struct type01
  {
   a,b,c;
  };
  // file : a.h
  # "type.h"
  ……
  // file : b.h
  # "type.h"
  ……
  // file .cpp
  # "a.h"
  # "b.h"
   (void)
  {
  ……
  }
  编译编译器给出以下提示:
  error C2011: “type01” : “struct”类型重定义
  原因是头文件type.h定义了个struct类型type01头文件a.h和b.h都包含了头文件type.h而在.cpp文件里却同时包含了头文件a.h和b.h因此出现了重定义
  可以通过像以下那样改写type.h文件从而避免重定义:
  // file : type.h
  #ndef __TYPE_H__ // 如果标记没被设置
  # __TYPE_H__ // 设置标记
  struct type01
  {
   a,b,c;
  };
  #end // End of __TYPE_H__
  通过这样改写type.h文件后可以顺利编译过去了
  我们是通过测试预处理器标记来检查type.h头文件是否已经包含过了如果这个标记没有设置表示这个头文件没有被包含过则应该设计标记反的,如果这个标记已经设置则表示这个头文件已经被包含所以应该忽略
*/

/*
C为类中提供类成员化列表

类对象构造顺序是这样:
1.分配内存构造隐式/显示化各数据成员
2.进入构造后在构造中执行般计算

使用化列表有两个原因:

1.必须这样做:
如果我们有个类成员它本身是个类或者是个结构而且这个成员它只有个带参数构造而没有默认构造这时要对这个类成员进行就必须这个类成员带参数构造如果没有化列表那么他将无法完成第就会报错

ABC
...{
public:
ABC( x, y, z);
private:
a;
b;
c;
};
MyClass
...{
public:
MyClass:abc(1,2,3)...{}
private:
ABC abc;
};

ABC有了显示带参数构造那么他是无法依靠编译器生成无参构造所以没有 3个型数据就无法创建ABC对象
ABC类对象是MyClass成员想要化这个对象abc那就只能用成员化列表没有其他办法将参数传递给ABC类构造
种情况是这样:当类成员中含有个const对象时或者是个引用时他们也必须要通过成员化列表进行这两种对象要在声明后马上而在构造是对他们赋值这样是不被允许

2.效率要求这样做:
类对象构造顺序显示进入构造体后进行是计算是对他们赋值操作显然赋值和化是区别这样就体现出了效率差异如果不用成员化类表那么类对自己类成员分别进行次隐式默认构造次复制操作符如果是类对象这样做效率就得不到保障

注意:构造需要数据成员不论是否显示出现在构造成员化列表中都会在该处完成并且顺序和其在声明时顺序是和列表先后顺序无关所以要特别注意保证两者顺序致才能真正保证其效率

为了介绍说明清楚假设有这样个类:
foo{
private :
a, b;
};
1、foo{}和foo( i = 0){}都被认为是默认构造后者是默认参数两者不能同时出现
2、构造列表化方式不是按照列表顺序而是按照变量声明顺序比如foo里面a在b的前那么会先构造a再构造b所以无论 foo:a(b + 1), b(2){}还是foo:b(2),a(b+1){}都不会让a得到期望如果先声明b再声明a则会更好
3、构造列表能够对const成员比如foo里面有 const c;则foo( x) : c(x){}可以让c值赋成x不过需要注意c必须在每个构造(如果有多个)都有值
4、在继承里面只有化列表可以构造父类private成员比如说
child : public foo{
}
foo里面构造是这样写:foo ( x) { a = x; }.
而在child里面写child( x){ foo(x); }是通过不了编译只有把父类化改为foo( x) : a(x){}而子类构造写作child ( x) : foo(x){}才可以

篇有关化列表文章:

C化类成员不但可以用构造(constructor)完成而且可以用化类成员列表来完成MFC大量用到此思路方法例如有些初学者可能不大理解如下代码:
A
{
public:
member_var; //成员变量
A; //构造
}
A::A:member_var(0)
{
}

他们觉得这个构造定义应该只能这样写:
A::A
{
member_var=1;
}

其实两种思路方法都可但是有些情况下只能用第而且通常情况下用第种也会效率高些

其实种思路方法是真正化(initialization),而在构造内实现“=”操作其实是赋值(assign)这两种思路方法切区别从这儿开始区别大概如下:

我们知道普通变量编译器都会默认替你他们既能也能被赋值而常量(const)按照其意思只能被不能赋值否则和变量就无区别了所以常量成员(const member)只能用成员化列表来完成他们化”而不能在构造内为他们“赋值”
我们知道类对象化其实就是构造完成如果没有写构造编译器会为你默认生成如果你自定义了带参数构造那么编译器将不生成默认构造这样这个类对象化必须有参数如果这样对象来做另外某个类成员那么为了化这个成员你必须为这个类对象构造传递个参数同样如果你在包含它这个类构造里用“=”其实是为这个对象“赋值”而非“化”它所以个类里所有构造都是有参数那么这样类如果做为别成员变量你必须显式化它你也是只能通过成员化列表来完成例如:
B
{
......
}

A
{
public:
B member_b;
A;
}
A::A:B(...) //你必须显式化它所有构造
//都是有参数的后才能被赋值
{
B=...; //如上所写已经化了才能被赋值否则
}



——————————————————————————————————————

化顺序:

test
{

const a;

std: str;

object o;

test:str(“df”),o(null),a(0)

{

}

};



黄色既是化列表他们会在构造正式前被且他们化顺序并不是根据 化列表中出现顺序而是他们声明顺序来如上:

化顺序是:a, str, o;

般用于化 常量类型静态类型数据或者不能独立存在数据
*/

/*
C11个注意要点



下面这些要点是对所有C员都适用我的所以说它们是最重要这些要点中提到是你通常在C书中或网站WebSite上无法找到如:指向成员指针这是许多资料中都不愿提到地方也是经常出错地方甚至是对些高级C员也是如此
  这里要点不仅仅是解释怎样写出更好代码更多是展现出语言规则里面东西很显然它们对C员来说是永久好资料我相信这篇文章会使你收获不小

  首先我把些由区别层次C员经常问问题归到我惊奇发现有很多是有经验员都还没意识到 .h 符号是否还应该出现在标准头文件中


要点1: <iostream.h> 还是 <iostream>?

  很多C员还在使用<iostream.h>而不是用更新标准<iostream>库这两者都有什么区别呢?首先5年前我们就开始反对把.h符号继续用在标准头文件中继续使用过时规则可不是个好思路方法从功能性角度来讲<iostream>包含了系列模板化I/O类相反地<iostream.h>只仅仅是支持另外输入输出流C标准规范标准接口在些微妙细节上都已改进因此<iostream>和<iostream.h>在接口和执行上都是区别最后<iostream>各组成都是以STL形式声明然而<iostream.h>各组成都是声明成全局型

  这些实质上区别你不能在中混淆使用这两个库做为种习惯在新代码中般使用<iostream>但如果你处理是过去编写代码为了继承可以用继续用<iostream.h>旧保持代码致性  


要点2:用引用传递参数时应注意地方

  在用引用传递参数时最好把引用声明为const类型这样做好处是:告诉不能修改这个参数在下面这个例子中f就是传递引用:

void f(const & i);

{
 f(2); /* OK */
}

  这个传递个参数2给f在运行时C创建个值为2类型临时变量并传递它引用给f.这个临时变量和它引用从f开始被创建并存在直到返回返回时就被马上删除注意如果我们不在引用前加上const限定词f可能会更改它参数更可能会使产生意想不到行为所以别忘了const

  这个要点也适用于用户定义对象你可以给临时对象也加上引用如果是const类型:

struct A{};
void f(const A& a);

{
 f(A); // OK,传递个临时Aconst引用
}


要点3:“逗号分离”表达形式

 “逗号分离”表达形式是从C继承来使用在for-和while-循环中当然这条语法规则被认为是不直观首先我们来看看什么是“逗号分离”表达形式

  个表达式由个或多个其它表达式构成由逗号分开如:

 (x, --y, cin.good) // 3个表达式
  这个条件包含了 3个由逗号分离表达式C会计算每个表达式但完整“逗号分离”表达式结果是最右边表达式因此仅当cin.good返回true时条件值才是true下面是另个例子:
j=10;
i=0;
while( i, --j)
{
 //直到j=0时循环结束在循环时i不断自加
}

要点4使用全局对象构造启动前

  有些应用需要在主启动前其它如:转态过程、登记功能都是必须在实际运行前被最简单办法是通过个全局对象构造这些全局对象都是在主开始前被构造这些都将会在的前返回结果如:
Logger
{

 public:
 Logger
  {
   activate_log;//译者注:在构造你需要先运行
  }
};
Logger log; //个全局例子


{
 record * prec=read_log;//译者注:读取log文件数据
 //.. 代码
}


  全局对象log在运行的前被构造logactivate_log从而开始执行时它就可以从log文件中读取数据


  毫无疑问地在C编程中内存管理是最复杂和最容易出现bug地方直接访问原始内存、动态分配存储和最大限度发挥C指令效率都使你必须尽力避免

有关内存bug
  
要点5:避免使用复杂构造指向指针

  指向指针是C中可读性最差语法的你能告诉我下面语句意思吗?

void (*p[10]) (void (*));
  P是个“由10个指针构成指向个返回void类型且指向另个无返回和无运算这个麻烦语法真是让人难以辨认不是吗?你其实可以简单通过typedef来声明相当于上面语句首先使用typedef声明“指向个无返回和无运算指针”:
typedef void (*pfv);
  接着声明“另个指向无返回且使用pfv指针”:
typedef void (*pf_taking_pfv) (pfv);
  现在声明个由10个上面这样指针构成:
pf_taking_pfv p[10];
  和void (*p[10]) (void (*))达到同样效果但这样是不是更具有可读性
了!

要点6:指向成员指针

  个类有两种基本成员:成员和数据成员同样指向成员指针也有两种:指向成员指针和指向数据成员指针后则其实并不常用般是不含有公共数据成员仅当用在继承用C写代码时协调结构(struct)和类()时才会用到

  指向成员指针是C语法中最难以理解构造的但是这也是个C最强大特性它可以让你个类成员而不必知道这个名字个非常敏捷工具同样你也可以通过使用指向数据成员指针来检查并改变这个数据而不必知道它成员名字

  指向数据成员指针

  尽管刚开始时指向成员指针语法会使你有点点迷惑但你不久会发现它其实同普通指针差不多只不过是*号前面多了::符号和类名字例:定义个指向指针:


* pi;
  定义个指向为数据成员:
A::*pmi; //pmi是指向类A成员
  你可以这样化它:
A
{
 public:
  num;
  x;
};
A::*pmi = & A::num;
  上面代码是声明个指向类Anum成员并将它化为这个num成员地址.通过在pmi前面加上*你就可以使用和更改类Anum成员值:
A a1, a2;
n=a1.*pmi; //把a1.num赋值给n
a1.*pmi=5; // 把5赋值给a1.num
a2.*pmi=6; // 把6赋值给6a2.num

  如果你定义了个指向类A指针那么上面操作你必须用 ->*操作符代替:
A * pa= A;
n=pa->*pmi;
pa->*pmi=5;

  指向成员指针

  它由成员所返回数据类型构成类名后跟上::符号、指针名和参数列表举个例子:个指向类A成员(该返回类型)指针:

A
{
 public:
  func ;
};
(A::*pmf) ;

  上面定义也就是说pmf是个指向类A成员func指针.实际上这个指针和个普通指向指针没什么区别只是它包含了类名字和::符号你可以在在任何使用*pmf地方这个
func:
pmf=&A::func;
A a;
(a.*pmf); //a.func
  如果你先定义了个指向对象指针那么上面操作要用->*代替:
A *pa=&a;
(pa->*pmf); //pa->func
  指向成员指针要考虑多态性所以当你通过指针个虚成员时这个将会被动态回收个需要注意地方你不能取个类构造和析构地址

要点7、避免产生内存碎片


  经常会有这样情况:你应用每运行次时就自身缺陷而产生内存漏洞而泄漏内存而你又在周期性地重复着你结果可想而知,它也会使系统崩溃但怎样做才能预防呢?首先尽量少使用动态内存在大多数情况下你可能使用静态或自动存储或者是STL容器第 2尽量分配大块内存而不是次只分配少量内存举个例子:次分配例子所需内存而不是次只分配元素内存

要点8、是delete还是delete

  在员中有个荒诞说法:使用delete来代替delete删除类型时是可以
  举个例子吧:

  *p= [10];
 delete p; //应该是:delete p
  上面是完全事实上个平台上使用delete代替delete应用也许不会造成系统崩溃但那纯粹是运气你不能保证你应用是不是会在另个编译器上编译在另个平台上运行所以还是请使用delete

要点9、优化成员排列

  个类大小可以被下面方式改变:

struct A

{
 bool a;
  b;
 bool c;
}; // (A) 12

  在我电脑上 (A) 等于12这个结果可能会让你吃惊A成员总数是6个字节:1+4+1个字节那另6字节是哪儿来?编译器在每个bool成员后面都插入了3个填充字节以保证每个成员都是按4字节排列以便分界你可以减少A大小通过以下方式:

struct B
{
 bool a;
 bool c;
  b;
}; // (B) 8

  这编译器只在成员c后插入了2个字节b占了4个字节所以就很自然地把它当作个字形式排列而a和c大小1+1=2再加上2个字节就刚好按两个字形式排列B

要点10、为什么继承个没有虚析构类是危险

  个没有虚析构类意味着不能做为个基类如std::,std::complex, 和 std::vector 都是这样为什么继承个没有虚析构类是危险?当你公有继承创建个从基类继承相关类时指向新类对象中指针和引用实际上都指向了起源对象析构不是虚所以当你delete个这样类时C就不会析构举个例子介绍说明:

A
{
 public:
 ~A // 不是虚
 {
 // ...
 }
};
B: public A //错; A没有虚析构
{
 public:
 ~B
 {
 // ...
 }
};


{
 A * p = B; //看上去是对
 delete p; //错B析构函没有被
}

要点11、以友元类声明嵌套

  当你以友元类声明个嵌套类时把友元声明放在嵌套类声明后面而不前面

A
{
 private:
  i;
 public:
  B //嵌套类声明在前
 {
  public:
  B(A & a) { a.i=0;};
 };
 friend B;//友元类声明
};

  如果你把友元类声明放在声明嵌套类前面编译器将抛弃友元类后其它声明


*/

/*
浅谈内存泄漏



对于个c/c员来说内存泄漏是个常见也是令人头疼问题已经有许多技术被研究出来以应对这个问题比如Smart PoerGarbage Collection等Smart Poer技术比较成熟STL中已经包含支持Smart Poer但是它使用似乎并不广泛而且它也不能解决所有问题;Garbage Collection技术在Java中已经比较成熟但是在c/c领域发展并不顺畅虽然很早就有人研究在C中也加入GC支持现实世界就是这样作为个c/c内存泄漏是你心中永远不过好在现在有许多工具能够帮助我们验证内存泄漏存在找出发生问题代码

内存泄漏定义

般我们常说内存泄漏是指堆内存泄漏堆内存是指从堆中分配大小任意(内存块大小可以在运行期决定)使用完后必须显示释放内存应用般使用mallocrealloc从堆中分配到块内存使用完后必须负责相应free或delete释放该内存块否则这块内存就不能被再次使用我们就说这块内存泄漏了以下这段小演示了堆内存发生泄漏情形:

void MyFunction( nSize)
{
char* p= char[nSize];
( !GetStringFrom( p, nSize ) ){
MessageBox(“Error”);
;
}
…//using the poed by p;
delete p;
}


GetStringFrom返回零时候指针p指向内存就不会被释放这是种常见发生内存泄漏情形在入口处分配内存在出口处释放内存但是c可以在任何地方退出所以旦有某个出口处没有释放应该释放内存就会发生内存泄漏

广义内存泄漏不仅仅包含堆内存泄漏还包含系统资源泄漏(resource leak)比如核心态HANDLEGDI ObjectSOCKET Interface等从根本上说这些由操作系统分配对象也消耗内存如果这些对象发生泄漏最终也会导致内存泄漏而且某些对象消耗是核心态内存这些对象严重泄漏时会导致整个操作系统不稳定所以相比的下系统资源泄漏比堆内存泄漏更为严重

GDI Object泄漏是种常见资源泄漏:
void CMyView::OnPa( CDC* pDC )
{
CBitmap bmp;
CBitmap* pOldBmp;
bmp.LoadBitmap(IDB_MYBMP);
pOldBmp = pDC->SelectObject( &bmp );

( Something ){
;
}
pDC->SelectObject( pOldBmp );
;
}
Something返回非零时候在退出前没有把pOldBmp选回pDC中这会导致pOldBmp指向HBITMAP对象发生泄漏这个如果长时间运行可能会导致整个系统花屏这种问题在Win9x下比较容易暴露出来Win9xGDI堆比Win2k或NT要小很多

内存泄漏发生方式:

以发生方式来分类内存泄漏可以分为4类:

1. 常发性内存泄漏发生内存泄漏代码会被多次执行到每次被执行时候都会导致块内存泄漏比如例 2如果Something直返回True那么pOldBmp指向HBITMAP对象总是发生泄漏

2. 偶发性内存泄漏发生内存泄漏代码只有在某些特定环境或操作过程下才会发生比如例 2如果Something只有在特定环境下才返回True那么pOldBmp指向HBITMAP对象并不总是发生泄漏常发性和偶发性是相对对于特定环境偶发性也许就变成了常发性所以测试环境和测试思路方法对检测内存泄漏至关重要

3. 次性内存泄漏发生内存泄漏代码只会被执行或者由于算法上缺陷导致总会有块仅且块内存发生泄漏比如在类构造中分配内存在析构中却没有释放该内存但是这个类是个Singleton所以内存泄漏只会发生个例子:

char* g_lpszFileName = NULL;

void SetFileName( const char* lpcszFileName )

{

( g_lpszFileName ){

free( g_lpszFileName );

}

g_lpszFileName = strdup( lpcszFileName );

}
例 3

如果在结束时候没有释放g_lpszFileName指向那么即使多次SetFileName总会有块内存而且仅有块内存发生泄漏

4. 隐式内存泄漏在运行过程中不停分配内存但是直到结束时候才释放内存严格说这里并没有发生内存泄漏最终释放了所有申请内存但是对于个服务器需要运行几天几周甚至几个月不及时释放内存也可能导致最终耗尽系统所有内存所以我们称这类内存泄漏为隐式内存泄漏个例子:

Connection
{
public:
Connection( SOCKET s);
~Connection;

private:
SOCKET _;

};

ConnectionManager
{
public:
ConnectionManager{
}
~ConnectionManager{
list ::iterator it;
for( it = _connlist.begin; it != _connlist.end; it ){
delete (*it);
}
_connlist.clear;
}
void _disibledevent=>_connlist.push_back(p);
}

void _disibledevent=>⑤l或h:l对整型指long型对实型指double型h用于将整型格式修正为

---------------------------------------
格式
格式用以指定输出项数据类型和输出格式
①d格式:用来输出十进制整数有以下几种使用方法:
%d:按整型数据实际长度输出
%md:m为指定输出字段宽度如果数据位数小于m则左端补以空格若大于m则按实际位数输出
%ld:输出长整型数据
②o格式:以无符号 8进制形式输出整数对长整型可以用"%lo"格式输出同样也可以指定字段宽度用“%mo”格式输出
例:

{ a = -1;
prf("%d, %o", a, a);
}
运行结果:-1,177777
解析:-1在内存单元中(以补码形式存放)为(1111111111111111)2转换为 8进制数为(177777)8
③x格式:以无符号十 6进制形式输出整数对长整型可以用"%lx"格式输出同样也可以指定字段宽度用"%mx"格式输出
④u格式:以无符号十进制形式输出整数对长整型可以用"%lu"格式输出同样也可以指定字段宽度用“%mu”格式输出
⑤c格式:输出
⑥s格式:用来输出个串有几中使用方法
%s:例如:prf("%s", "CHINA")输出"CHINA"串(不包括双引号)
%ms:输出串占m列串本身长度大于m则突破获m限制,将串全部输出若串长小于m则左补空格
%-ms:如果串长小于m则在m列范围内串向左靠右补空格
%m.ns:输出占m列但只取串中左端n个这n个输出在m列右侧左补空格
%-m.ns:其中m、n含义同上n个输出在m列范围左侧右补空格如果n>m则自动取n值即保证n个正常输出
⑦f格式:用来输出实数(包括单、双精度)以小数形式输出有以下几种使用方法:
%f:不指定宽度整数部分全部输出并输出6位小数
%m.nf:输出共占m列其中有n位小数如数值宽度小于m左端补空格
%-m.nf:输出共占n列其中有n位小数如数值宽度小于m右端补空格
⑧e格式:以指数形式输出实数可用以下形式:
%e:数字部分(又称尾数)输出6位小数指数部分占5位或4位
%m.ne和%-m.ne:m、n和”-”含义和前相同此处n指数据数字部分小数位数m表示整个输出数据所占宽度
⑨g格式:自动选f格式或e格式中较短种输出且不输出无意义

---------------------------------------
有关prf步介绍说明:
如果想输出"%",则应该在“格式控制”串中用连续两个%表示如:
prf("%f%%", 1.0/3);
输出0.333333%

---------------------------------------
对于单精度数使用%f格式符输出时仅前7位是有效数字小数6位.
对于双精度数使用%lf格式符输出时前16位是有效数字小数6位.


*/

/*
STL中map使用方法详解



Map是STL个关联容器它提供(其中第个可以称为关键字每个关键字只能在map中出现第 2个可能称为该关键字值)数据处理能力由于这个特性它完成有可能在我们处理数据时候在编程上提供快速通道这里说下map内部数据组织map内部自建颗红黑树(种非严格意义上平衡 2叉树)这颗树具有对数据自动排序功能所以在map内部所有数据都是有序后边我们会见识到有序好处

下面举例介绍说明什么是数据映射比如个班级中每个学生学号跟他姓名就存在着映射关系这个模型用map可能轻易描述很明显学号用描述姓名用串描述(本篇文章中不用char *来描述而是采用STL中来描述),下面给出map描述代码:

Map<, > mapStudent;

1. map构造

map共提供了6个构造这块涉及到内存分配器这些东西略过不表在下面我们将接触到些map构造思路方法这里要说下就是我们通常用如下思路方法构造个map:

Map<, > mapStudent;

2. 数据插入

在构造map容器后我们就可以往里面插入数据了这里讲 3种插入数据思路方法:

种:用insert插入pair数据下面举例介绍说明(以下代码虽然是随手写应该可以在VC和GCC下编译通过大家可以运行下看什么效果在VC下请加入这条语句屏蔽4786警告 #pragma warning (disable:4786) )

# <map>

# <>

# <iostream>

Using std;

Int

{

Map<, > mapStudent;

mapStudent.insert(pair<, >(1, “student_one”));

mapStudent.insert(pair<, >(2, “student_two”));

mapStudent.insert(pair<, >(3, “student_three”));

map<, >::iterator iter;

for(iter = mapStudent.begin; iter != mapStudent.end; iter)

{

Cout<<iter->first<<” ”<<iter->second<<end;

}

}

第 2种:用insert插入value_type数据下面举例介绍说明

# <map>

# <>

# <iostream>

Using std;

Int

{

Map<, > mapStudent;

mapStudent.insert(map<, >::value_type (1, “student_one”));

mapStudent.insert(map<, >::value_type (2, “student_two”));

mapStudent.insert(map<, >::value_type (3, “student_three”));

map<, >::iterator iter;

for(iter = mapStudent.begin; iter != mapStudent.end; iter)

{

Cout<<iter->first<<” ”<<iter->second<<end;

}

}

第 3种:用方式插入数据下面举例介绍说明

# <map>

# <>

# <iostream>

Using std;

Int

{

Map<, > mapStudent;

mapStudent[1] = “student_one”;

mapStudent[2] = “student_two”;

mapStudent[3] = “student_three”;

map<, >::iterator iter;

for(iter = mapStudent.begin; iter != mapStudent.end; iter)

{

Cout<<iter->first<<” ”<<iter->second<<end;

}

}

以上 3种使用方法虽然都可以实现数据插入但是它们是有区别当然了第种和第 2种在效果上是完成用insert插入数据在数据插入上涉及到集合性这个概念即当map中有这个关键字时insert操作是插入数据不了但是用方式就区别了它可以覆盖以前该关键字对应介绍说明

mapStudent.insert(map<, >::value_type (1, “student_one”));

mapStudent.insert(map<, >::value_type (1, “student_two”));

上面这两条语句执行后map中1这个关键字对应值是“student_one”第 2条语句并没有生效那么这就涉及到我们如何知道insert语句是否插入成功问题了可以用pair来获得是否插入成功如下

Pair<map<, >::iterator, bool> Insert_Pair;

Insert_Pair = mapStudent.insert(map<, >::value_type (1, “student_one”));

我们通过pair第 2个变量来知道是否插入成功个变量返回个map迭代器如果插入成功话Insert_Pair.second应该是true否则为false

下面给出完成代码演示插入成功和否问题

# <map>

# <>

# <iostream>

Using std;

Int

{

Map<, > mapStudent;

Pair<map<, >::iterator, bool> Insert_Pair;

Insert_Pair = mapStudent.insert(pair<, >(1, “student_one”));

If(Insert_Pair.second true)

{

Cout<<”Insert Successfully”<<endl;

}

Else

{

Cout<<”Insert Failure”<<endl;

}

Insert_Pair = mapStudent.insert(pair<, >(1, “student_two”));

If(Insert_Pair.second true)

{

Cout<<”Insert Successfully”<<endl;

}

Else

{

Cout<<”Insert Failure”<<endl;

}

map<, >::iterator iter;

for(iter = mapStudent.begin; iter != mapStudent.end; iter)

{

Cout<<iter->first<<” ”<<iter->second<<end;

}

}

大家可以用如下看下用插入在数据覆盖上效果

# <map>

# <>

# <iostream>

Using std;

Int

{

Map<, > mapStudent;

mapStudent[1] = “student_one”;

mapStudent[1] = “student_two”;

mapStudent[2] = “student_three”;

map<, >::iterator iter;

for(iter = mapStudent.begin; iter != mapStudent.end; iter)

{

Cout<<iter->first<<” ”<<iter->second<<end;

}

}

3. map大小

在往map里面插入了数据我们如何知道当前已经插入了多少数据呢可以用size使用方法如下:

Int nSize = mapStudent.size;

4. 数据遍历

这里也提供 3种思路方法对map进行遍历

种:应用前向迭代器上面举例中到处都是了略过不表

第 2种:应用反相迭代器下面举例介绍说明要体会效果请自个动手运行

# <map>

# <>

# <iostream>

Using std;

Int

{

Map<, > mapStudent;

mapStudent.insert(pair<, >(1, “student_one”));

mapStudent.insert(pair<, >(2, “student_two”));

mapStudent.insert(pair<, >(3, “student_three”));

map<, >::reverse_iterator iter;

for(iter = mapStudent.rbegin; iter != mapStudent.rend; iter)

{

Cout<<iter->first<<” ”<<iter->second<<end;

}

}

第 3种:用方式介绍说明如下

# <map>

# <>

# <iostream>

Using std;

Int

{

Map<, > mapStudent;

mapStudent.insert(pair<, >(1, “student_one”));

mapStudent.insert(pair<, >(2, “student_two”));

mapStudent.insert(pair<, >(3, “student_three”));

nSize = mapStudent.size

//此处有误应该是 for( nIndex = 1; nIndex <= nSize; nIndex)


//by rainfish

for( nIndex = 0; nIndex < nSize; nIndex)

{

Cout<<mapStudent[nIndex]<<end;

}

}

5. 数据查找(包括判定这个关键字是否在map中出现)

在这里我们将体会map在数据插入时保证有序好处

要判定个数据(关键字)是否在map中出现思路方法比较多这里标题虽然是数据查找在这里将穿插着大量map基本使用方法

这里给出 3种数据查找思路方法

种:用count来判定关键字是否出现其缺点是无法定位数据出现位置,由于map特性映射关系就决定了count返回值只有两个要么是0要么是1出现情况当然是返回1了

第 2种:用find来定位数据出现位置它返回个迭代器当数据出现时它返回数据所在位置迭代器如果map中没有要查找数据它返回迭代器等于end返回迭代器介绍说明

# <map>

# <>

# <iostream>

Using std;

Int

{

Map<, > mapStudent;

mapStudent.insert(pair<, >(1, “student_one”));

mapStudent.insert(pair<, >(2, “student_two”));

mapStudent.insert(pair<, >(3, “student_three”));

map<, >::iterator iter;

iter = mapStudent.find(1);

(iter != mapStudent.end)

{

Cout<<”Find, the value is ”<<iter->second<<endl;

}

Else

{

Cout<<”Do not Find”<<endl;

}

}

第 3种:这个思路方法用来判定数据是否出现是显得笨了点但是我打算在这里讲解

Lower_bound使用方法这个用来返回要查找关键字下界(是个迭代器)

Upper_bound使用方法这个用来返回要查找关键字上界(是个迭代器)

例如:map中已经插入了1234如果lower_bound(2)返回2而upper-bound(2)返回就是3

Equal_range返回个pairpair里面第个变量是Lower_bound返回迭代器pair里面第 2个迭代器是Upper_bound返回迭代器如果这两个迭代器相等则介绍说明map中不出现这个关键字介绍说明

# <map>

# <>

# <iostream>

Using std;

Int

{

Map<, > mapStudent;

mapStudent[1] = “student_one”;

mapStudent[3] = “student_three”;

mapStudent[5] = “student_five”;

map<, >::iterator iter;

iter = mapStudent.lower_bound(2);

{

//返回是下界3迭代器

Cout<<iter->second<<endl;

}

iter = mapStudent.lower_bound(3);

{

//返回是下界3迭代器

Cout<<iter->second<<endl;

}



iter = mapStudent.upper_bound(2);

{

//返回是上界3迭代器

Cout<<iter->second<<endl;

}

iter = mapStudent.upper_bound(3);

{

//返回是上界5迭代器

Cout<<iter->second<<endl;

}



Pair<map<, >::iterator, map<, >::iterator> mapPair;

mapPair = mapStudent.equal_range(2);

(mapPair.first mapPair.second)
{

cout<<”Do not Find”<<endl;

}

Else

{

Cout<<”Find”<<endl;
}

mapPair = mapStudent.equal_range(3);

(mapPair.first mapPair.second)
{

cout<<”Do not Find”<<endl;

}

Else

{

Cout<<”Find”<<endl;
}

}

6. 数据清空和判空

清空map中数据可以用clear判定map中是否有数据可以用empty它返回true则介绍说明是空map

7. 数据删除

这里要用到erase它有 3个重载了下面在例子中详细介绍说明它们使用方法

# <map>

# <>

# <iostream>

Using std;

Int

{

Map<, > mapStudent;

mapStudent.insert(pair<, >(1, “student_one”));

mapStudent.insert(pair<, >(2, “student_two”));

mapStudent.insert(pair<, >(3, “student_three”));



//如果你要演示输出效果请选择以下你看到效果会比较好

//如果要删除1,用迭代器删除

map<, >::iterator iter;

iter = mapStudent.find(1);

mapStudent.erase(iter);



//如果要删除1用关键字删除

Int n = mapStudent.erase(1);//如果删除了会返回1否则返回0



//用迭代器成片删除

//下代码把整个map清空

mapStudent.earse(mapStudent.begin, mapStudent.end);

//成片删除要注意也是STL特性删除区间是个前闭后开集合



//自个加上遍历代码打印输出吧

}

8. 其他使用方法

这里有swap,key_comp,value_comp,get_allocator等感觉到这些在编程用不是很多略过不表有兴趣话可以自个研究

9. 排序

这里要讲点比较高深使用方法了,排序问题STL中默认是采用小于号来排序以上代码在排序上是不存在任何问题上面关键字是它本身支持小于号运算些特殊情况比如关键字是个结构体涉及到排序就会出现问题它没有小于号操作insert等在编译时候过不去下面给出两个思路方法解决这个问题

种:小于号重载举例

# <map>

# <>

Using std;

Typedef struct tagStudentInfo

{

Int nID;

String strName;

}StudentInfo, *PStudentInfo; //学生信息



Int

{

nSize;

//用学生信息映射分数

map<StudentInfo, >mapStudent;

map<StudentInfo, >::iterator iter;

StudentInfo studentInfo;

studentInfo.nID = 1;

studentInfo.strName = “student_one”;

mapStudent.insert(pair<StudentInfo, >(studentInfo, 90));

studentInfo.nID = 2;

studentInfo.strName = “student_two”;

mapStudent.insert(pair<StudentInfo, >(studentInfo, 80));



for (iter=mapStudent.begin; iter!=mapStudent.end; iter)

cout<<iter->first.nID<<endl<<iter->first.strName<<endl<<iter->second<<endl;



}

以上是无法编译通过只要重载小于号就OK了如下:

Typedef struct tagStudentInfo

{

Int nID;

String strName;

Bool operator < (tagStudentInfo const& _A) const

{

//这个指定排序策略按nID排序如果nID相等按strName排序

If(nID < _A.nID) true;

If(nID _A.nID) strName.compare(_A.strName) < 0;

Return false;

}

}StudentInfo, *PStudentInfo; //学生信息

第 2种:仿应用这个时候结构体中没有直接小于号重载介绍说明

# <map>

# <>

Using std;

Typedef struct tagStudentInfo

{

Int nID;

String strName;

}StudentInfo, *PStudentInfo; //学生信息



Classs sort

{

Public:

Bool operator (StudentInfo const &_A, StudentInfo const &_B) const

{

If(_A.nID < _B.nID) true;

If(_A.nID _B.nID) _A.strName.compare(_B.strName) < 0;

Return false;

}

};



Int

{

//用学生信息映射分数

Map<StudentInfo, , sort>mapStudent;

StudentInfo studentInfo;

studentInfo.nID = 1;

studentInfo.strName = “student_one”;

mapStudent.insert(pair<StudentInfo, >(studentInfo, 90));

studentInfo.nID = 2;

studentInfo.strName = “student_two”;

mapStudent.insert(pair<StudentInfo, >(studentInfo, 80));

}

10. 另外

由于STL是个统整体map很多使用方法都和STL中其它东西结合在比如在排序上这里默认用是小于号即less<>如果要从大到小排序呢这里涉及到东西很多在此无法加以介绍说明

还要介绍说明map中由于它内部有序由红黑树保证因此很多执行时间复杂度都是log2N如果用map可以实现功能而STL Algorithm也可以完成该功能建议用map自带效率高

下面说下map在空间上特性否则估计你用起来会有时候表现比较郁闷由于map每个数据对应红黑树上个节点这个节点在不保存你数据时是占用16个字节个父节点指针左右孩子指针还有个枚举值(标示红黑相当于平衡 2叉树中平衡因子)我想大家应该知道这些地方很费内存了吧不说了……


*/

/*
深入理解



最近在论坛里总有人问有关问题并且本人对这个问题也直没有得到很好解决索性今天对它来个较为详细整理总结同时结合strlen进行比较如果能对大家有点点帮助这是我最大欣慰了

  、好首先看看和strlen在MSDN上定义:

  首先看MSDN上如何对进行定义:

Operator
expression
The keyword gives the amount of storage, in s, associated with a variable or a type
(including aggregate types). This keyword s a value of type size_t.
The expression is either an identier or a type-cast expression (a type specier
enclosed in parentheses).
When applied to a structure type or variable, s the actual size, which may
padding s inserted for alignment. When applied to a ally dimensioned .gif' />,
s the size of the entire .gif' />. The operator cannot the size of dynamically
allocated .gif' />s or external .gif' />s.
  然后再看下对strlen是如何定义:

strlen
Get the length of a .
Routine Required Header:
strlen <.h>
size_t strlen( const char * );
Parameter
:Null-terminated
Libraries
All versions of the C run-time libraries.
Return Value
Each of these functions s the number of characters in , excluding the terminal
NULL. No value is reserved to indicate an error.
Remarks
Each of these functions s the number of characters in , not including the
terminating null character. wcslen is a wide-character version of strlen; the argument of
wcslen is a wide-character . wcslen and strlen behave identically otherwise.

   2、由几个例子说开去

  第个例子:

char* ss = "0123456789";
(ss) 结果 4 ===ss是指向串常量指针
(*ss) 结果 1 ===*ss是第
char ss = "0123456789";
(ss) 结果 11 ===ss是计算到\\\\\\\\\\\\\\\\0位置因此是10+1
(*ss) 结果 1 ===*ss是第
char ss[100] = "0123456789";
(ss) 结果是100 ===ss表示在内存中大小 100×1
strlen(ss) 结果是10 ===strlen是个内部实现是用个循环计算到\\\\\\\\\\\\\\\\0为止的前
ss[100] = "0123456789";
(ss) 结果 400 ===ss表示再内存中大小 100×4
strlen(ss) ===strlen参数只能是char* 且必须是以\\\\\\\\\\\\\\\'\\\\\\\\\\\\\\\'结尾
char q="abc";
char p="a\\\\\\\\\\\\\\\\n";
(q),(p),strlen(q),strlen(p);
结果是 4 3 3 2
  第 2个例子:

X
{
i;
j;
char k;
};
X x;
cout<<(X)<<endl; 结果 12 ===内存补齐
cout<<(x)<<endl; 结果 12 同上
  第 3个例子:

char szPath[MAX_PATH]
  如果在内这样定义那么(szPath)将会是MAX_PATH但是将szPath作为虚参声明时(void fun(char szPath[MAX_PATH])),(szPath)却会是4(指针大小)

   3、深入理解

  1.操作符结果类型是size_t它在头文件中typedef为unsigned 类型该类型保证能容纳实现所建立最大对象字节大小
  2.是算符strlen是
  3.可以用类型做参数strlen只能用char*做参数且必须是以\\\\\\\\\\\\\\\'\\\\\\\\\\\\\\\'\\\\\\\\\\\\\\\\0\\\\\\\\\\\\\\\'\\\\\\\\\\\\\\\'结尾还可以用做参数比如:
f;
prf("%d\\\\\\\\\\\\\\\\n", (f));
输出结果是()即2
  4.参数不退化传递给strlen就退化为指针了
  5.大部分编译 在编译时候就把计算过了 是类型或是变量长度这就是(x)可以用来定义维数原因
char str[20]="0123456789";
a=strlen(str); //a=10;
b=(str); //而b=20;
  6.strlen结果要在运行时候才能计算出来时用来计算长度不是类型占内存大小
  7.后如果是类型必须加括弧如果是变量名可以不加括弧这是是个操作符不是个
  8.当适用了于个结构类型时或变量 返回实际大小 当适用静态地空间 归还全部尺 寸 操作符不能返回动态地被分派了或外部尺寸
  9.作为参数传给时传是指针而不是传递首地址如:
fun(char [8])
fun(char )
都等价于 fun(char *) 在C里传递永远都是传递指向首元素指针编译器不知道大小如果想在内知道大小 需要这样做:进入后用memcpy拷贝出来长度由另个形参传进去
fun(unsiged char *p1, len)
{
unsigned char* buf = unsigned char[len+1]
memcpy(buf, p1, len);
}
  有关内容见: C PRIMER?
  10.计算结构变量大小就必须讨论数据对齐问题为了CPU存取速度最快(这同CPU取数操作有关详细介绍可以参考些计算机原理方面书)C在处理数据时经常把结构变量中成员大小按照4或8倍数计算这就叫数据对齐(data alignment)这样做可能会浪费些内存但理论上速度快了当然这样设置会在读写些别应用生成数据文件或交换数据时带来不便MS VC对齐设定有时候得到和实际不等般在VC中加上#pragma pack(n)设定即可.或者如果要按字节存储而不进行数据对齐可以在Options对话框中修改Advanced compiler页中Data alignment为按字节对齐
11.操作符不能用于类型不完全类型或位字段不完全类型指具有未知存储大小数据类型如未知存储大小类型、未知内容结构或联合类型、void类型等(max)若此时变量max定义为 max,(char_v) 若此时char_v定义为char char_v [MAX]且MAX未知(void)都不是正确形式
   4、结束语

  使用场合

  1.操作符个主要用途是和存储分配和I/O系统那样例程进行通信例如: 
  void *malloc(size_t size), 
  size_t fread(void * ptr,size_t size,size_t nmemb,FILE * stream)
  2.用它可以看看类型对象在内存中所占单元字节
void * mem(void * s, c,(s))
  3.在动态分配对象时,可以让系统知道要分配多少内存
  4.便于些类型扩充,在windows中就有很多结构内型就有个专用字段是用来放该类型字节大小
  5.由于操作数字节数在实现时可能出现变化建议在涉及到操作数字节大小时用来代替常量计算
  6.如果操作数是形参或类型形参给出其指针大小
*/

Tags:  java学习路线图

延伸阅读

最新评论

发表评论