编程语言

img wwwooowww

c++ 学习

发表于2004/10/5 23:53:00  2332人阅读

一.获取时间   请问如何计算一个程序运行的时间? 用什么函数来获得当前时间?
1.用difftime,秒级

#include <time.h>
#include <stdio.h>
#include <dos.h>

int main(void)
{
   time_t first, second;

   first = time(NULL);  /* Gets system   time */

  // 你的程序    ...............


   second = time(NULL); /* Gets system time    again */

   printf("The difference is: %f seconds/n",difftime(second,first));
   getch();

   return 0;
}

2.用DWORD GetTickCount(VOID)
CTime WINAPI GetCurrentTime( ) throw( );
获得更精确的时间 GetTickCount

3.获取系统编译程序的时间
char* time1 = __DATE__;
char* time2 = __TIME__;
其中__DATE__和__TIME__是俩个宏。

二,IO算子

cout<<"Hello world!"<<flush<<endl; ---强制输出
   cout<<endl;
   cout<<ends;          -----输出一个null字符
   cout<<setfill('*');   -----和setw配合使用,将空白区用指定字符输出
   cout<<setw(14)<<"how"<<setw(14)<<"how"<<endl;--set width
   double d = 33.11444;
   cout<<setprecision(4)<<d<<endl;----设置小数点的位数
   cout<<dec<<10<<endl;----一旦设定,后面的全部跟随。
 
   cout<<hex<<10<<endl;
   cout<<oct<<10<<endl;
   setbase(n);----更改原来设置的进制。如更改为16进制,setbase(16);
   int a;
   cin>>hex>>a;   ----以hex输入
   cout<<dec<<a<<endl;
 
 
三、内存对齐问题(关键字,内存对齐)
typedef struct
{
 int a[10];
 char ch[10];
 int f[10];
 bool b[10];
}MType;


int _tmain(int argc, _TCHAR* argv[])
{

 int i;
    MType mt; 
 
 printf("%d",sizeof(MType));
 getch();
 return 0;
}
这个结构中,本来按我们计算sizeof(MType)是100,然而结果是104。在vc和
linux下的dev c++都是。
原因是内存对齐问题。一般编译器认为内存以4的倍数对齐。仔细分析内存可以得出
结论,不够4,要向后移动,凑成4的倍数。这个机构的一个对象就涉及了向后移动2
字节,补全2字节。故为104。

可以在文件最前面加#pragma pack(1) 这是将编译器该成为1的倍数对齐,这样求出的
长度就为100了。

四。分位定义
typedef unsigned char _uint_8;
typedef struct
{
 char lo4:4, //先定义的是低位,在内存中,高位在左, 低位在右。
      hi4:4;
}myByte;
int main()
{

 myByte mch;
 void* p;
 //mch.hig4 = 6;
 //mch.low4 = 1;
 p = &mch;
 //*((unsigned char*)p) = *((unsigned char*)p)>>4;
 *(unsigned char*)(p) = 0xf1;
 printf("%d /n", mch.hig4); //-1 结果
 printf("%d /n", mch.low4); //1
 
 *(unsigned char*)(p) >>= 4;
 
 printf("%d /n", mch.hig4); //0
 printf("%d /n", mch.low4); //-1
 
 
 return 0;
}
表示将一个字节分成俩部分,前面2位,后面6位。
使用:

 MyByte b;
 b.hi4 =  10;
 b.lo4 = 2;

 cout<<b.hi4<<endl;
 cout<<b.lo4<<endl;
注意:不能夸字节定义。必须定义在结构里方可使用。

五,关于new


new"分配"的内存当然都在堆中。
但new并不仅仅只有分配内存的作用,它还可以在已有内存(包括栈内存)上强制重新
构造对象的作用。

如:
#include <iostream>
using namespace std;

class A
{
public:
 int i;
 int j;
 A(int _i , int _j) { i = _i; j = _j; }
};

int main()
{
 A a(3, 4);
 cout << a.i << a.j << endl;
 new (&a) A(5,6);
 cout << a.i << a.j << endl;

 return 0;
}

六、关于n个随机数的和的问题
求m个随机数之和为n的算法。

void myRan(int n,int m)
{
 int i;
 int sum=0;
 int *a = new int[m];
 for (i=0; i<m; i++)
 {
  a[i] = 0;
 }
 for(i=0; i<m-1; i++)
 {
  a[i]=rand()%(n + 1-sum);
  sum+=a[i];
  cout<<a[i]<<'/t';
 }
 a[m-1]=n - sum;
 cout<<a[m-1]<<endl;
 delete []a;

}
七,关于结构的定义和赋值
typedef struct
{
   int a;
   short b[2];                  /*定义第一个结构体*/                   
}Ex2;
typedef struct Ex1
{
    int a;
    char b[3];
    Ex2 c;                     /*定义第二个结构体*/
 Ex1 * d;
}Ex1;
我们看第二个结构,Ex1 必须写在括号的前面,否这编译不会通过,大概是不识别
结构种的Ex1符号吧。而后一个Ex1可以写,也可以不写。
如果结构种没有自身的指针,那么,机构的名字可以放在结构定义的末尾,
也可以是结构体的开始。 另外,结构体种不能用结构自身定义成员,可以用结构
的指针定义成员例如:
typedef struct Ex1
{
    int a;
    char b[3];
    Ex2 c;                     /*定义第二个结构体*/
 Ex1 d;  //错误,可以用结构的指针,但是不能用结构自身定义对象。
}Ex1;

结构体的赋值:
对于上面的俩个结构:
typedef struct
{
   int a;
   short b[2];                  /*定义第一个结构体*/                   
}Ex2;
typedef struct Ex1
{
    int a;
    char b[3];
    Ex2 c;                     /*定义第二个结构体*/
 Ex1 * d;
}Ex1;
上面是c语言的定义方式,在C++中,可以省略掉typedef,比如:
struct Ex1
{
    int a;
    char b[3];
    Ex2 c;                     /*定义第二个结构体*/
 Ex1 * d;
};
这时,c++编译器可以识别出Ex1,并且可以直接用Ex1定义对象,不用再加strcut。
比如:Ex1 e1;

所以关键看是什么编译器,我用g++ 编译后者没有错误。用gcc就出现了好多错误。
可见后面的是c++支持的。在c++编程思想上有说明。Ch2,P12
可以这样赋值。
 Ex2 e2 = {10,{10,10}};
 Ex1 e1={10,"Hi",{5,{25,25}},0};
 
八,关于内存问题:
从总体上程序的内存空间可分为代码区和数据区。
从C++的角度来看数据区又可作如下划分:
1.
自动存储区(栈):自动(局部)变量、寄存器变量(声明的寄存器变量可能在寄存器
中,也可能在一般内存中。在逻辑上寄存器属于自动存储区。)、临时对象以及函数参
数。
2.
静态存储区:全局对象、函数中的静态变量、类中的静态数据成员、常量字符串以及
namespace 变量(比如 namespace abc = std;中的 abc。)
3. 自由存储区(堆):也称为动态内存。

一个可执行文件、.o、.a文件中系统规定的段有这么多
* .bss
该sectiopn保存着未初始化的数据,这些数据存在于程序内存映象中。通过定义,当程
序开始运行,系统初始化那些数据为0。该section不占文件空间,正如它的section类
型SHT_NOBITS指示的一样。
* .comment
该section保存着版本控制信息。
* .data and .data1
这些sections保存着初始化了的数据,那些数据存在于程序内存映象中。
* .debug
该section保存着为标号调试的信息。该内容是未指明的。
* .dynamic
该section保存着动态连接的信息。该section的属性将包括SHF_ALLOC位。是否需要SHF
_WRITE是跟处理器有关。第二部分有更详细的信息。
* .dynstr
该section保存着动态连接时需要的字符串,一般情况下,名字字符串关联着符号表的
入口。第二部分有更详细的信息。
* .dynsym
该section保存着动态符号表,如“Symbol Table”的描述。第二部分有更
详细的信息。
* .fini
该section保存着可执行指令,它构成了进程的终止代码。
因此,当一个程序正常退出时,系统安排执行这个section的中的代码。
* .got
该section保存着全局的偏移量表。看第一部分的“Special
Sections”和第二部分的“Global Offset Table”获得更多的信息。
* .hash
该section保存着一个标号的哈希表。看第二部分的“Hash Table”获得更多的信息。
* .init
该section保存着可执行指令,它构成了进程的初始化代码。
因此,当一个程序开始运行时,在main函数被调用之前(c语言称为main),系统安排执
行这个section的中的代码。
* .interp
该section保存了程序的解释程序(interpreter)的路径。假如在这个section中有一个
可装载的段,那么该section的属性的SHF_ALLOC位将被设置;否则,该位不会被设置。
看第二部分获得更多的信息。
* .line
该section包含编辑字符的行数信息,它描述源程序与机器代码之间的对于关系。该sect
ion内容不明确的。
* .note
该section保存一些信息,使用“Note Section”(在第二部分)中提到的格式。
* .plt
该section保存着过程连接表(Procedure Linkage Table)。看第一部分的``Special
Sections''和第二部分的“Procedure Linkage Table”。
* .rel<name> and .rela<name>
这些section保存着重定位的信息,看下面的``Relocation''描述。
假如文件包含了一个可装载的段,并且这个段是重定位的,那么该section的
属性将设此,一个重定位的section适用的是.text,那么该名字就为.rel.text或者是.
rela.text。
* .rodata and .rodata1
这些section保存着只读数据,在进程映象中构造不可写的段。看第二部分的`Program
Header''获得更多的资料。
* .shstrtab
该section保存着section名称。
* .strtab
该section保存着字符串,一般地,描述名字的字符串和一个标号的入口相关
联。假如文件有一个可装载的段,并且该段包括了符号字符串表,那么section的SHF_A
LLOC属性将被设置;否则不设置。
* .symtab
该section保存着一个符号表,正如在这个section里``Symbol
Table''的描述。假如文件有一个可装载的段,并且该段包含了符号表,那么section的
SHF_ALLOC属性将被设置;否则不设置。
* .text
该section保存着程序的``text''或者说是可执行指令。

前缀是点(.)的section名是系统保留的,尽管应用程序可以用那些保留的section名。
应用程序可以使用不带前缀的名字以避免和系统的
sections冲突。object文件格式可以让一个定义的section部分不出现在上面的列表中
。一个object文件可以有多个同样名字的 section。
引用:
简单地说有如下结构
命令行参数和环境变量


未初始化数据段bss
初始数据段
正文段


C程序一直由下列部分组成:

1)正文段——CPU执行的机器指令部分;一个程序只有一个副本;只读,防止程序由于意
外事故而修改自身指令;
2)初始化数据段(数据段)——在程序中所有赋了初值的全局变量,存放在这里。
3)非初始化数据段(bss段)——在程序中没有初始化的全局变量;内核将此段初始化为
0。
4)栈——增长方向:自顶向下增长;自动变量以及每次函数调用时所需要保存的信息(
返回地址;环境信息)。
5)堆——动态存储分。

|-----------|
| |
|-----------|
| 栈 |
|-----------|
| | |
| /|/ |
| |
| |
| /|/ |
| | |
|-----------|
| 堆 |
|-----------|
| 未初始化 |
|-----------|
| 初始化 |
|-----------|
| 正文段 |
|-----------|


http://community.csdn.net/Expert/topic/3215/3215569.xml?temp=.3182337

       low address
   +--------------------------------+
   |   _TEXT class 'CODP'           |
   |        code                    |
   +--------------------------------+--
   |   _DATA class 'DATA'           |
   |     initialized data           |
   +--------------------------------+ DGROUP
   |   _BSS class 'BSS'             |
   |     uninitialized data         |
   +--------------------------------+--
   |                                |
   |     FREE SPACE                 |
   +--------------------------------+
   |                                |
   |     STACK                      |
   +--------------------------------+
   |                                |
   |     HEAP                       |
   +--------------------------------+
   |                                |
   |     FREE SPACE                 |
   +--------------------------------+
         high address

九、怎么快速检测出一个巨大的链表中的死链?我实在没有想出好办法,
只能说,没办法,只能一个一个的比了...各位高手有没有什么好办法?
死链:链表的某个接点的next指针指向了曾经出现过的接点,导致链表死循环。
刚才忘了说:该链表是单链。

老迈的解决办法,很牛:
以前的帖子里有这道题的简化版的答案,我再贴一次吧,呵呵。
如果只是判断链表中是否有回路而不用具体求出回路的具体路径的话,则有一个取巧的
办法。
开两个遍历链表的指针,一个一次往前走一个结点,另一个往前走两个结点,
当这两个指针相遇时,如果该接点的next==null,则无回路,否则说明有死链。

十,关于句柄类的信息,看c++编程思想Ch3,p12。

一,
为分削得人憔悴,
跺上高楼,
跺上高楼,
原来众人都在楼上。

二,关于数组的赋值:
struct Node
{
 int n;
 float f;
 char ch;
};
int main()
{
 int str[20] = {1,50,3};//剩余元素都为0。只要有个,剩余都为0;
 //但是如果没有初始化,int str[20];这时,数组并不初始化为0。而是任意值
 Node nodeArr[20] = {{1,1.1,'a'}};//在没有构造函数的情况下,
 //和前目数组一样,剩余节点都初始化为0。
 //除第一个节点外,后面的个元素{0,0.0000,0}
 int i;
 for(i=0; i<20; i++)
 {
   printf("%d  ",str[i]);
   printf("%d  %f  %c/n",nodeArr[i].n, nodeArr[i].f, nodeArr[i].ch);
 }
 printf("/n");
 return 0;
}

但是在有了构造函数的情况下,用法就不同上面:
struct Node
{
 int n;
 float f;
 char ch;
 Node();
 
};
Node::Node()//默认构造函数
{
 n=1;
 f = 1.1;
 ch = 'b';
}
int main()
{
 //int str[20] = {1,50,3};
 //Node nodeArr[20] =
{{1,1.1,'a'}};//这种用法编译错误,要求用构造函数构造对象
 Node nodeArr[20] = {};// 每个Node对象调用默认构造函数,内部成员分别为
         // 1,1.1, 'b'
 //可见:如果没有构造函数,就用与c相同的语法编译,
 //如果有了构造函数,就必须用构造函数进行数组的初始化。这里就构和类是一样

 int i;
 for(i=0; i<20; i++)
 {
   //printf("%d  ",str[i]);
   printf("%d  %f  %c/n",nodeArr[i].n, nodeArr[i].f, nodeArr[i].ch);
 }
 printf("/n");
 return 0;
}

四,一个有趣的面试题:
哪为高手能帮帮我,
在不使用if,while,do~~while等语句,不使用关系运算符,不使用MAX(),MIN()
的情况下,怎样比较输入的a,b两个数的大小。(注:实现并不知道输入的数的大小)
float max(float a,float b)
{
   float t1,t2;
   t1 = a + b;
   t2 = sqrt((a-b)*(a-b));//免得你说fabs中也用了判断语句
   return (t1 + t2)/2;
}
这里藏着很好的道理。t1 是二者的和,t2是大着比小者大的部分。如果把大出的
部分加到小者的上面,正好是2倍的大者。

三,关于省缺参数的定义。
如果一个程序只有一个cpp文件,而且这个函数没有函数声名,只有函数定义,那么
这个函数的省缺参数可以放到函数定义中。比如:这种情况:
#include <iostream>
using namespace std;
void fun1(int m = 3, char ch = 'b')
{}
int main ()
{

   fun1();
   fun2();
   return 0;
}

如果一个程序,有头文件,在头文件中有函数声名,或者没有头文件,但是有函数声名
这种情况下,省缺参数必须放在函数的声名中,不能放在函数定义中,否则编译错误。
比如:
//links.h
void fun2(int m=3,char ch='a');


//links.cpp
#include <iostream>
#include "links.h"
using namespace std;
void fun1(int m = 3, char ch = 'b');//有声名,必须在声名处给出省缺参数
void fun2(int m/*=3*/,char
ch/*='a'*/)//声名中给出,再不能在定义中设置省缺参数
{
}
void fun1(int m /*=3*/, char ch/* =
'b'*)//声名中给出,再不能在定义中设置省缺参数
{}
void fun3(int m=3, char ch='c')//没有函数声名,可以在定义时给出省缺参数
{}
int main ()
{

   fun1();
   fun2();
   fun3();
   return 0;
}

四,分布式系统
分布式系统是通过通信网络将物理上分布的具有自治功能的数据处理系统或计算机
系统互联起来,实现信息交换和资源共享,协作完成任务。分布式系统要求一个统
一的操作系统,实现系统操作的统一性。分布式操作系统管理分布式系统中的所有
资源,它负责全系统的资源分配和调度,任务划分,信息传输控制协调工作,并为
用户提供一个统一的界面,用户通过这一界面实现所需要的操作并使用系统资源,
至于操作定在哪一台计算机上执行或使用哪台计算机的资源则是操作系统完成的,
用户不必知道。此外,由于分布式系统更强调分布式计算和处理,因此对于多机合
作和系统重构,健壮性和容错能力有更高的要求,要求分布式操作系统有更短的相
应时间,更高吞吐量和更高可靠性。

五,冒泡排序算法:
void order(char* chs, const int& n)
{//冒泡排序算法,zhangggdlt
 int i,j;
 int maxSubscript;
 for (i=0; i<n; i++)//n-1
 {
  chs[0] = chs[1];
  maxSubscript = 1;
  for (j=2; j<=n-i; j++)
  {
   if (chs[0]<chs[j])
   {
    chs[0] = chs[j];
    maxSubscript = j;
   }
  }
  chs[maxSubscript] = chs[n-i];
  chs[n-i] = chs[0];
 }
 chs[0] = ' ';
}
六、关于命名空间
namespace A{
#define MIN 20
int k = 3;
}
其实,#define MIN 20 可以穿透命名空间,
也就是说不受命名空间限制,所以放入放出无所谓,建议放在外面。
构造函数不能是静态函数。

七:关于多字节数据类型在内存中的存储问题:
int ,short 分别是4、2字节。他们的存储方式是:
int data = 0xf4f3f2f1;
其中低位存放在编址小的内存单元,高位存放在编址搞的内存单元
地址:0x8000      0x8001    0x8002   0x8003
数据: f1           f2         f3      f4

八、枚举型定义,枚举元素的值可以重复。
typedef enum
{
 monday=1, cat,tuesday=1,dog
}week;
用法:
week w = dog;
int k = tuesday;
上面都是可以的。

此时,dog的值是它前面的值加一,里面有俩个元素值是相等的。同理,cat也一样。
cat 和 dog的值都是2。

九,获取时间,包括星期的程序
#include<time.h>
#include <stdio.h>


int main(){
char *wday[]={"Sun","Mon","Tue","Wed","Thu","Fri","Sat"};
time_t timep;
struct tm *p;
time(&timep);
p=localtime(&timep); /*取得当地时间*/
printf ("%d.%d.%d ", (1900+p->tm_year),(1 +p->tm_mon), p->tm_mday);
//注意月份载localtime是从0。。11 代表1到12月,所以要加1。
printf("%s  %d:%d:%d/n", wday[p->tm_wday],p->tm_hour, p->tm_min, p->tm_sec);
return 0;
}

十、各个分析
#include <iostream>
using namespace std;

class A{
 int x;
};

class B:public A
{public:
    int b1;
 B(){b1=2;}
 int geti(){
  cout<<"geti called,b1 is "<<b1<<endl;
  return 1;
 }
};

int main()
{
 
 A a;
 B b;
 int k;
 B* pb=(B*)&a;
 pb = (B*)&k;
 pb->geti();
 int i;
    cin>>i;
 return 0;


}

分析:
你的类A是基类,类B是子类。
把基类的对象a的地址,强制转换成子类的地址。这样很危险,会导致数据裁减。其实
是更本不希望这样做的。强制类型转换,可以将任何类型都可以转换,虽然语法没有错
误,但是语意上没有意义。比如:我们可以
int k;
B* pb=(B*)&k;
这样编译也没有错误,但是,更本没有意义。

你的转换同样也是一个道理。你的a对象里跟本就没有B类函数geti所要用的东西,但是
对于计算机来说,它不管这些,它只要对指针所指的内存进行操作就行了,它认为从指
针所指的位置开始,后面就是一个B类对象。所以编译可以通过。
但是没有意义。

一、数字加密算法DES(Data Encryption Standard)

二、在linux下,最后连接成可执行文件时,如果连接的是一般的.o文件,则整个文件
的内容都会被装入可执行文件中;如果连接的是库,则只是从库中找到程序中用到的变
量和函数,将他们装入可执行文件中,那些放在库中但是没有被程序所引用的变量和函
数则不会被连接到可执行文件中。所以使用库可以节省大量的开发时间,写较大的程序
时,把程序模块放到库中。

三、一个类型问题
int PacketInfo::packetLen(const void* packet)
{
 u_int_8 tempByte[4]; //use to store、exchange the info from the
packet
 int ethType;
 int packetLen = 0;
 memcpy(tempByte, (u_int_8*)packet+12, 2);
 ......
}
我得函数中要实现内存拷贝,memcpy的原型是定义函数  void * memcpy (void * dest
,const void *src, size_t n);
 我开始用memcpy(tempByte, packet+12,
2);编译报告错误,后来换成memcpy((void*)tempByte, (void*)packet+12, 2);
 也不能, 原来错误在一个空类型指针和一个数字相加,
不能得到类型所占空间大小,于是无法确定packet+12的地址。
还有这个例子:
void memecopy(void* dp, const void* sp, int num)
{
 int i=0;
 while(i<num)
 {
  *((unsigned char*)dp)++ = *((unsigned char*)sp)++;
  i++;
 }
}
开始我没有注意,只是*dp++ = *sp++; 呵呵, 结果当然编译不过去,对一个void指针加加,
系统当然不知道干什么了。

四、在c++中, 函数没有层次关系,可以有下面的函数调用情况。
void funB();

void funA()
{
 funB();
}
void funB()
{
 funA();
}
只要函数调用前对被调用函数声名了就可以了。

五、内联函数的使用限制
内联函数目的是为了提高函数执行效率(当用到内联函数时,
不进行函数调用,而是类似于宏替换),内联函数定义有一下限制。
1、函数中不可以有静态变量
2、函数中不能太复杂,不能有switch、goto等语句
3、不能递归
4、不能定义数组。
如果出现了上述情况,编译器会自动忽略了inline,将其编译为一般函数。

六、在内存中, 数是以补码形式存在的, 整数的补码仍然为整数,
负数的补码是正数的各位(包括符号位)取反加1。
比如:
#include <stdio.h>
int main()
{
 char ch = -3; //-1:11111111   -2:11111110 -127:10000001 -128:10000000
 unsigned char value;
 for(int i=0; i<8; i++)
 {
  value = ((ch<<i) & 0x80)>>7; //11111101
  printf("%d ", value);
 }
 printf("/n");
 return 0;
}

七、linux 下 ip地址转换函数:
 #include <sys/socket.h>
 #include <netinet/in.h>
 #include <arpa/inet.h>

  int inet_aton(const char *cp, struct in_addr *inp);

 in_addr_t inet_addr(const char *cp);

 in_addr_t inet_network(const char *cp);

 char *inet_ntoa(struct in_addr in);

 struct in_addr inet_makeaddr(int net, int host);

 in_addr_t inet_lnaof(struct in_addr in);

 in_addr_t inet_netof(struct in_addr in);

七、关于逗号算符
int t;
int i=0;
t=i++,++i,i++;
//结果t=1; i=3;
在c++课本上说, 逗号操作是强迫从左向右赋值。整个语句的值是最后一个分操作。
比如:
int a = 3;
a=3*5, a*4;//语句的结果是60
(a=3*5, a*4),a+5;//结果是20
int a=2, b=3, c=4;
print("%d, %d, %d/n", (a,b,c), b, c);
打印的第一个整数是(a,b,c),它的值是最后一个,所以是c,
最后的答案是:
4, 3, 4

也就是说, 上面的语句分成四部分,第一部分为t=i++, 第二部分为++i,第三部分为i++.
还有下面的例子:
int a[] = {1,2,3,4,5};
int *i=a,k;
int b = 0;
b = k=*i++,printf("%d->",k),k=*++i,printf("%d->",k),k=*i++,printf("%d->/n",k);
printf("%d/n", b);
输出为:
1->3->3->
1
(完)。

七.一(相关上面,printf()的怪异)
int array[5]={8,5,13,55,6};
int *p=array;
printf("%d,%d,%d/n",*p++,*++p,*p++);
printf("%d,%d/n",*p,++*p);
for(int i=0;i<5;i++)
 printf("%d,",array[i]);
结果为:
13,13,8
56,56
8,5,13,56,6,
非常不解,以后总结。
找到答案了,和一个同学讨论的结论。
在c++的函数中, 函数在压栈时, 参数是从右到左压栈的, 比如:
void pp(int a, int b, int c)
{
 int d;
 int e;
 d = 0;
 e = 2;
}
int main()
{
 ...
 pp(...);
 
}
这时, 函数pp的栈中有pp的参数, main返回地址, 和现场信息。
pp的参数压栈是:c, b , a。这样, 先压c, 那么要先算c, 也就是说先算最后
一个参数,对于函数printf("%d,%d,%d/n",*p++,*++p,*p++);来说,先算后一个*pp++,
得出0(c 最后一个参数),再算中间一个*++p得出2(b 中间一个参数), 最后算第一
个*p++(a 第一个参数)得出2。所以a=2, b= 2, c=0。
打出的是220。

好像printf()里如果涉及到每个变元参与运算,那么输出就要倒过来。

七.一 再谈printf()
printf 的格式中, 如果出现了/0 /数字(八进制的1到3位) /x数字的情况处理(十六进制
的1到2位)。
/0当然是字符串的结束标志了。
/101  这时, /与后面的1到三位八进制数字表示一个字符。注意,是按八进制算的, 不是
十进制。
/x61  /与后面的十六进制表示一个字符。
printf("/101 /x41");
结果是:
A A


八、关于sizeof问题

void Func(char a[100])
{
 char b[100];
 
 printf("%d/n", sizeof(a)); // 4字节而不是100字节
 printf("%d/n", sizeof(b)); //100
 
}
分析:俩个一样,不用的原因要追踪到函数的传参问题。当数组作为参数传入函数时,
这个数组作为指针传入,也就是所说的传地址。所以前面的是4,而不是100。

九、符号的结合方向。
在c++中, 运算符一般是从左向右结合的,只有赋值符是特例,从右向左结合。
比如:
a+b+c*d  从左向右结合
a = b = c =d;  从左向右结合。

十、再轮++、--操作。
int i = 3, k;
k = (++i)+(++i)+(++i);
k的值是18。原因是每个操作数每个操作数先++, 最后算加法。
i++三次成了6,最后是三个六相加,得18
int i=3, k;
k=(i++)+(i++)+(i++);
结果是9。原因是i先算加法,三个三相加,最后++,i=6。


一、在c语言中规定,凡不加类型说明得函数,一律自动按整型处理。在函数中,如果
返回类型与函数类型不同,则以函数类型为准,对数值型数据,可以自动进行数据类型
转换,即函数类型决定返回类型。如下:
max(x,y)
{
 float x, y, z;
 z = x>y?x:y;
 return (Z);
 
}
main()
{
 float a, b;
 int c;
 scanf("%f, %f",&a, &b);
 c = max(a,b);
 printf("Max is %d/n", c);
 
}
结果是:
1.5,2,5
Max is 2
max(x,y) 没有明显定义类型,按整型处理,返回一定是整型。
在c函数中,如果没有返类型和return,这是并不是说函数真没有return,而是要返回
一个用户无用得值,这个值不确定。比如:
//test.c
#include <stdio.h>
#include <string.h>
pp()
{}
pp1()
{
}
main()
{
 int c = 0;
 c = pp();
 printf("%d/n", c);

 c = pp1();
 printf("%d/n", c);
}
输出为:
0
2
这俩个值不确定。
如果函数不想要返回,则要用void 定义函数类型。这样就没有返回值了。

二、c函数定义的一般形式:
无参函数定义:
类型标识符 函数名()
{说明部分
   语句
}

有参函数定义的一般形式:
类型标识符 函数名字(形式参数列表)
形式参数说明
{说明部分
 语句
}
如:
int max(x,y)
int x,y; //形式参数说明部分
{int z;  //函数体中的说明部分
 z=x>y?x:y;
 return (z);
}

函数调用说明:
如果函数参数列表中包含多个实参,各系统对于实参求值的顺序是不确定的,有的系统
自左向右顺序求实参,有的自右向左,许多c版本(Turbo C 和MS C)是按自右向左的顺
序求值。这就导致了前面的printf("%d %d %d /n", *p++, *++p, *p++);的结论。

三、宏定义的范围
#define G 9.18
   ^
   |
   |  G的有效区域。
   |
   v

#undef G
宏定义后面不能加分号, 否则连分号一起作为宏定义的部分。宏定义不做类型检查,
在预编译时处理,只有展开后才会根据替换来决定是否正确。

四、函数参数和函数的局部变量
void pp(char str1[])
{
 int i;
 for(i=0; i<10; i++)
  printf("%c ", *str++); //1
 char str2[20];
 for(i=0; i<10; i++)
  printf("%c ", *str2++); //2
}
在这个函数中,函数的参数str1是一个数组,函数的局部变量str2也是一个数组。
但是在1处,编译是正确的,但是在2处,编译通不过。它们都是数组,但是,函数
参数如果是数组,那么当作地址处理,所以str1可以作为一个指针变量处理。可以用
地址的++运算,但是对于局部变量str2,str2是一个数组,数组的地址是一个常量,
不能++。
这和前面提到的sizeof的例子是一个道理。
void pp(int str[10])
{
 printf("%d /n", sizeof(str)); //4  只是地址。
 int str2[10];
 printf("%d /n", sizeof(str2)); //40   数组的大小。
}

五、指向函数的指针。
一般形式为:  数据类型 (*指针名字)(参数链表);
在c语言中,不要求参数链表,只在调用时候需要。但是c++中必须要求在函数指针定义
的时候给出参数链表。
比如:
//test.c
#include <stdio.h>
#include <string.h>
void pp(int a)
{
 printf("ok/n");
}
int main()

 void (*p)(); //c的函数指针定义, 可以不写出参数链表,没有错误。c++不行。
 p = pp; //在给函数指针赋值时,只需要给出函数名字而不必要给出参数
 (*p)(); //通过函数指针调用函数
 return 0;
}

//test.cpp
#include <stdio.h>
#include <string.h>
void pp(int a)
{
 printf("OK!/n");
}
int main()

 int a = 3;
 void (*p)();
 p = pp; //错误,必须定义:void (*p)(int);才可以。
 //p = p1;
 (*p)(a);
 return 0;
}
c的定义并不严格, 但是c++严格的多了,同样,c的函数可以不定义返回类型,但是
c++必须定义。

六、函数指针做函数参数
把函数名字作为参数传递给一个函数,这样的好处是:比较灵活,比如一个函数sub需
要调用俩个函数,如果直接在sub函数中调用,那么sub函数就写死了,如果想在sub中
调用一系列函数中的任意俩个,这时用函数指针做参数,可以根据赋值的不同而调用不同
的函数。
例如:
sub(int x, int y, int(*fun)());
{
 ....
 (*fun)();
}
....
int fun1()
{
 ...
}
main()
{
 int a,b;
 sub(a, b, fun1);
}

七、关于指针和数组联合的定义:

int (*pointer1)[4];
int *pointer2[4];
看二者的区别: 前面pointer1是指向一个有四个元素的一维数组的指针;后面pointer2,
一个是定义一个有四个元素的一维指针数组,数组的每个元素是一个指针变量。

八、回调函数浅谈

这是一种类似根据不同条件调用同一个返回值+参数类型一致的不同函数实现的机制.

针对Windows的消息机制一般回调函数指窗口函数, 根据不同消息都使用该窗口函数进
行操作.针对用户自己的应用也可以建立起相应的消息机制, 类似VB里面的事件
1. 声明一个函数指针
2. 提供函数实现的一方在初始化的时候将函数指针初始化到相应的实现函数注册到调
用者
3. 在特定事件/条件发生的时候, 调用者使用函数指针进行调用.
这就是回调函数的一般操作模式, 其实你如果学习COM就会发现, 里面
的ConnectionPoint也是利用这种方式进行的, 客户端注册自己的事件处理子程序给
服务队组件, 组件通过调用这些函数触发事件, 其实回调的精髓就是你规定函数形
式(返回值, 参数)我提供实现和具体的调用地址.

九、对回调函数的例子:
//test.cpp
#include <stdio.h>
typedef void (*pcb)(int); //为了简化函数指针类型的变量定义,提高程序的可
       //读性,我们需要把函数指针类型自定义一下。
       
void pp(int a) //回调函数,回调函数可以象普通函数一样被程序调用,
{    //但是只有它被当作参数传递给被调函数时才能称作回调函数。
 printf("OK!%d/n", a);
}

void called(int n, pcb p) //被调函数,因为它被用户程序调用,所以叫被调函数
{        //我想是这样,用户在调用上面的函数时,
        //需要自己实现一个pcb类型的回调函数:
 printf("OKK");
 (*p)(n);
}
int main()

 int n = 1;
 called(n, pp);  //把pp当作一个变量传递给called函数
      
 return 0;
}

一个类的成员如果想做回调函数,那么该成员一定要为静态成员,比如:
#include <stdio.h>
typedef void (*pcb)(int n);
class A
{
public:
  static void getN(int n)  //该成员一定为static, 否则编译错误
  {
   printf("OK Get it!/n");
  }
};

void pp(int n, pcb p)
{
 (*p)(n);
}
int main()

 A a;
 int n = 3;
 pp(n, a.getN);
 return 0;
}

参考:http://www-900.ibm.com/developerWorks/cn/linux/l-callback/index.shtml

                                                 (待续)


 

阅读全文
0 0

相关文章推荐

img
取 消
img