Points

9 篇文章 0 订阅

为什么有些头文件中的函数申明,找不到其定义部分的代码

 

汗~ 其实很多库函数都是已经被编译成静态库(*.lib)或目标代码(*.obj)文件的。它们的函数原型被写在相应的头文件中,相当于一个接口,被其他程序调用。具体的实现部分已经被编译好了。所以当然看不到函数体的定义。当你的程序被编译时,编译程序会取出静态库中的目标代码,链接到你的EXE文件中。

这就好比你写好的DLL文件,具体函数都已被编译好了,那个.h文件只是提供原型,供其他程序调用的。

其实Windows API函数就是这样,相关的原型都在windows.h中,可真正的的函数实现代码已被编译好了,封装在Kernel32.dll、User32.dll等这些系统DLL中。

讲到这里,你应该明白了吧?呵呵

 

 

程序在运行时是如何通过函数申明找到函数的定义处的

 

这要了解编译的过程。

首先,编译是按源文件进行的(如.CPP文件),先把所有的#include头文件组合起来,预处理(宏的替代,变量查错等),然后编译成目标文件(.OBJ),此时已经完成了编译。

然后是链接,可以把链接过程想象成从main()函数开始,把其中的只有申明没有函数定义的符号链接到对应的目标文件(编译后生成的),一般链接时会查找当前存放临时目标文件的文件夹(Debug文件夹?)以及系统环境变量指向的库文件夹,从而查找的函数的定义。具体的查找过程如下:

lib文件告诉编译器哪个dll能够导出哪些函数,然后编译器会把这些信息写入可执行文件的一个称作导入表的位置。程序运行的时候由操作系统读取导入表并加载这些dll,然后得到导入函数的线性地址, 并写回导入地址表(对可执行文件在内存中的映像)。如果没有显式指定, OS会按照一定的路径规则去搜寻dll。

 

总之,在编译的过程中,就已经确定了.H文件中函数申明和对应的函数定义(不是.H文件和.CPP文件)之间的映射,因为函数的申明跟定义一定是会在一个.CPP文件中一起编译的(实现头文件中的某个函数的.CPP文件一定要#include此头文件)

PS:
动态链接库:在运行时加载
静态链接库:在编译时直接联到可执行文件中
 
 

#define语句结束是没有;的。

函数申明的后面是有;的,很显然,函数申明不是宏定义,是基本的语句,当然要;结尾

 

error C2064: term doesnot evaluate to a function 项不是一个函数。可能的原因是项(变量)与某个函数名重名了

 

 

while ((c = getchar()) != EOF)   !=的优先级高于=,所以要把c=getchar()用括号括起

       //getchar()的执行过程:

//1.判断stdin流中是否有数据,当没有数据时,等待用户输入(按回车结束输入,此时内存中已经有数据了)

       //2.当有数据时,获得第一个字符。

 

#define EOF (-1)    //END OF FILE

 

\b是backspace键   32是blank的ascii码,或者用‘ ’也行,他想普通的字符一样。要输出blank  用(char)32,或者‘ ’。

 

字符和int型数据是自动转换的。(封箱)。

 


CDialogdlg;注意,在c++中申明类时如果采用默认构造函数,不需要写()

CDialogdlg();---------------》错的!!!!!!!!!!!!!!!!!!!

 


else if 的理解!

 

           if(c==''||c=='\t'||c=='\n')

                 InWord=false;

           elseif(!InWord)

           {

                 InWord=true;

                 nw++;

           }

 

Else if 表示了两个部分,可以理解为在上面的if条件不成立,同时本if条件成立的情况下,做什么事。

代码的else if (!InWord{ doSomething}意义就是:在c!=’ ’&&c!=’\t’&&c!=’\n’的情况,同时InWrodfalse的情况下doSomething

等价于if(c==' '||c=='\t'||c=='\n')

                 InWord=false;

           else

           {

 if(!InWord)

{

                       InWord=true;

                       nw++;

}

           }

 

 

The switchstatement providesanother way to write a multi-way branch that is particulary suitable when thecondition is whether some integer or character expression matches one of a setof constants.

 

在定义数组时,如charp[20],此时的p类似一个const 指针(char*const p),其值不可以改变。(p=…../p++/p—都是错的)

 

在函数申明中定义的数组如void f(char p[]),此时的p被定义为一个指向char的指针,等同于void f(char* p),其是可以有赋值操作的(p=…../p++/p—都是对的)

…….很显然p被定义为了一个指针,不然不同长度的数组被传递给f,形参p如何初始化呢。

      分析f(constchar p[ ]):p仍然是一个指向char的指针,p的值可以变,但是数组是const型的,所以*p的值是不能变的!

 


Console程序中,Ctrl+z表示EOF(end of file) .整数值是-1


An external variable(全局变量)must be difined, exactly once, outside of any function, this sets aside storagefor it. The variable must also be declared in each function that wants toaccess it.

Syntactically, external definetions are just likedefintions of local variables, but since they outside of functions, the variablesare external. Before a function can use an external variable, the name of thevariable must be made known to the function; the

Declaration is the same as before except for the addedkeyword

extern.

In certain circumstances, theextern declaration can be omitted. If the definition of the external variableoccurs in the source file before its use in a particular function, then thereis no need for an

extern declaration in the function. The commonpractice is to place

definitions of all external variables at the beginningof the source

file, and then omit all extern declaration.

归纳起来就是,全局变量的定义是在所有函数之外的,当函数需要利用到此全局变量时,必须先用extern申明一下。但是当此全局变量和使用此变量的函数在同一个源文件中时,就可以省略extern申明。通常我们的做法是,将全局变量和需要使用此全局变量的函数写在同一个源文件中,而省略所有的extern申明。

当a源文件需要访问b源文件中的全局变量时,a中必须显示申明(extern)此变量,通常extern被定义在


C中,变量的命名是字母/数字/下划线,其中数字不能放在第一个位置,通常第一个是小写字母,某些系统变量采用的是_为变量起始。常量通常是用所有字母都是大写来表示。


There are only a fewbasic data types in C:

char a single byte, capable of holdingone character in the local character set

int an integer, typically reflectingthe natural size of integers on the host

machine

float single-precisionfloating point

double double-precisionfloating point

 

0x12:十六进制的12   077:八进制的77(前面的标识是零)


The complete set of escape sequences is


strlen()和其他的一些操作字符串的函数在<string.h>中


‘a’和“a”的区别:

‘a’:一个整数,代表的是字母a的ascii值。

“a”:一个字符串,其中包含两个字符‘a’和‘\0’。

 


当字符串被当做参数传入函数时(通常参数是指向字符串的指针),其值是在函数中不能够被改变的,因为字符串为const量

只有字符数组被当做参数传入函数时,其值才可以有赋值操作。


<ctype.h> define a family of functions thatprovide tests(such as function isdigit()) and conversions() that areindependent of character set.

 

Conversion rules are morecomplicated when unsigned operands are involved.The problem is that comparisonsbetween signed and unsigned values are machine-dependent, because they dependon the sizes of the various integer types.For example, suppose that int is 16bits and long is 32 bits. Then -1L < 1U,because 1U, which is an unsignedint, is promoted to a signed long. But -1L> 1UL because -1L is promoted tounsigned long and thus appears to be a large positive number.

 

 

 

对于浮点数来说,浮点数(float,double)实际上都是有符号数,unsigned 和signed前缀不能加在float和double之上,当然就不存在有符号数根无符号数之间转化的问题了。
 
While(s[i++]!=’\0’);循环结束时,i指向’\0’的后一位
While(s[i]!=’\0’)i++;循环结束时,i指向‘\0’;
 
 
 
/* strcat: concatenate t to end of s; s must be big enough
*/
void strcat(char s[], char t[])
{
int i, j;
i = j = 0;
while (s[i] != '\0') /* find end of s */
i++;
while ((s[i++] = t[j++]) != '\0') /* copy t */
;
}

 

 

C provides six operators for bitmanipulation; these may only be applied to integral

operands, that is, char, short, int, andlong, whether signed or unsigned.

& bitwise AND

| bitwise inclusive OR

^ bitwise exclusive OR

<< left shift

>> right shift

~ one's complement (unary)




The for statement

for(expr1; expr2; expr3)

statement

is equivalent to

expr1;

while(expr2) {

statement

expr3;

}

 

 

 

for (i = 0; isspace(s[i]); i++) /* skipwhite space */

;


当函数没有写返回类型时,int是默认的类型!

 

好的编程习惯也是必须的。在这种情况下,全局变量的定义宜放在某个 .c文件中,而 .h文件里写的是变量的声明。你如果不清楚变量应该定义在哪儿,干脆写个 globals.c,然后将变量定义加进去好了。1.h这个文件里不要写定义了,改成声明。注意,对多次全局变量定义的支持只是某些编译器的功能或者特性,而不是必须的。

其实说到底,对编程能力的考核还是在考察编程者的数学能力。

 

.CPP文件中出现extern int a;时,后面用到外部的a时,会查询所有.CPP文件找到a的定义,而不会查询.H文件!(a在头文件中定义没办法被使用),要使用,必须#include’ ’此头文件。

而定义在头文件中会出现当头文件被多个.CPP文件包含是,a被多次定义!对多次全局变量定义的支持只是某些编译器的功能或者特性,而不是必须的。所以,一般.H中放申明,而在.CPP文件中放定义(函数和变量)

 


Char a[]=”1234567”;

Sizeof(a):a数组所花费的字节数,值为8.

因为a是不分配内存的,其只是数组的标号!

 

Char* a=”1234567”;

Sizeof(a);a指针所花费的字节数,值为4.

因为a是指向char类型的指针,其是分配内存的,一个指针分配4个字节的内存

可见:数组名可以像一个const指针一样使用(其永远只能代表数组的首地址),但是其是不分配内存的!!!

 


如何得到数组a的元素个数:sizeof(a)/sizeof(aType)


逗号操作符的使用:例1:for(inti=0; i<5 && j<10; i++ , j++)

                                       2: inti=2, j=3;

 

mfc appwizard设置中,

当选择动态链接mfc library时,其减少了exe文件的大小,但是需要库文件存在于运行此程序的机器中,由于动态链接的库文件一般不小(还需要查找),所以速度相对static链接要慢些

当选择static链接时,exe的文件较大,因为加入了静态链接的文件。但是运行速度比动态链接稍快。

所以,他两在内存和时间上的各有优势。

但是动态链接更利于程序的修改

 


C++创建对象:

CRect*pRect = new CRect(250,50,300,100);对象指针

CRectrect;默认构造函数对象

CRectrect(250,50,300,100);此构造函数对象

 

Window message 分3类,窗口消息,命令消息,控制消息。

窗口消息:WM_PAINT等

命令消息:WM_COMMAND,菜单栏,工具栏中按钮的消息

控制消息:WM_COMMAND,窗口中的控件发出的给其所在的窗口的消息。

Windows message 、command message、control message

                     

对于command message,windows的消息传递过程是这样的:

SDI program:

1,        View object

2,        document object

3,        document template object

4,        main frame window object

5,        the application object

 

MDI program:

1,        the active view object

2,        the document object associated with theactive view object

3,        the document template object for the activedocument

4,        the frame window object for the activedocument templae

5,        the main frame window object

6,        the application object

 

 

Windows消息处理机制的理解:

 

 

Windows操作系统控制着所有消息的产生,传递和默认的处理过程,我们编程进行的只是对某些消息在传递的过程中进行拦截,通过我们自己的方法来处理此消息,从而达到我们的目的。

 

举个例子,当菜单栏中的一个下拉菜单中的某项被创建后,其就有了一系列的消息,这些消息何时产生,如何处理,windows都有默认的方式。

而我们通过创建消息句柄,消息处理函数来实现对其默认行为的修改。这些修改只是对已经存在的消息进行自定义的处理,而消息的产生和传递都是固定的。

 

所以说,控件,鼠标点击等等操作何时产生消息,产生什么样的消息,都是操作系统定义好的(而我们只能去学习,去接受),我们能做的就是在相应的消息下完成具体的工作。

 

 

消息原本就存在,我们只是处理其中某个时段产生的某个消息的行为。

我们不处理,系统就默认处理(通常什么不做)。

 

 

 

MFC中变量的初始化:

1,        类的初始化有其自己的方法,另论

2,        非类的初始化,就当一般的数据类型初始化,都是用=来赋值的!

 

例如:

POINT p={12,12};

POINT是结构体,其是={}形式

 

  COLORREF m_Color=RGB(0,0,0);

COLORREF 是DWORD类型,就是=的形式,RGB()是宏,其结果是一个DWORD值

 

Mfc中一些宏定义的函数参数该如何记住:一般都是以此函数的功能,参数的功能来缩写成大写字母加下划线打头的

 

参数 nPenStyle    宏定义的变量:PS_SOLID …….  

 


 

对于pen 、brush的创建和使用,一般有两种方式,一种是自己创建相关对象,然后通过pdc->selectobject()添加进去,另一种是利用系统已经创建的常用的pen,brush,font等对象,利用

pdc->selectstockObject()来添加。

1 CPen apen;

 apen.createPen(PS_SOLID,4,RGB(255,0,0));

  CPen*oldPen=pdc->selectObject(&apen);

 

2 pdc->selectStockObject(WHITE_BRUSH);


 

C#:构造器Constructor是否可被override?
答:构造器Constructor不能被继承,因此不能重写Overriding,但可以被重载Overloading。

Override: 重写,表示子类重写父类函数,两个函数的定义完全一样。

Overload: 重载,函数名相同,但是函数的参数不同。

重载与覆盖的区别?
答:1、方法的覆盖是子类和父类之间的关系,是垂直关系;方法的重载是同一个类中方法之间的关系,是水平关系
                 2、覆盖只能由一个方法,或只能由一对方法产生关系;方法的重载是多个方法之间的关系。
                 3、覆盖要求参数列表相同;重载要求参数列表不同。
                 4、覆盖关系中,调用那个方法体,是根据对象的类型(对像对应存储空间类型)来决定;重载关系,是根据调用时的实参表与形参表来选择方法体的。




struct结构的初始化方法

struct是C中重要的ADT。但是在一般讲C的书中,往往只介绍了struct的定义、顺序初始化及位域。
  
  为了方便后面的介绍,先定义一个struct类型:
   struct User
   {
   int id; //id
   char name[100]; //user name
   char *home; //home directory
   int passwd; //password
   };
  
  
  1 初始化
  struct数据有3中初始化方法:顺序,C风格及C++风格的乱序。
  
  1)顺序
  这种方法很常见,在一般的介绍C的书中都有介绍。顺序初始化的特点是: 按照成员定义的顺序,从前到后逐个初始化;允许只初始化部分成员;在被初始化的成员之前,不能有未初始化的成员。
  eg:
   struct User oneUser = {10, "Lucy","/home/Lucy"};
  初始化之后,oneUser各个成员的值为:
   oneUser.id = 10;
   oneUser.name = "Lucy";
   oneUser.home = "/home/Lucy";
   oneUser.passwd = 0;
  
  2)乱序(C风格)
  顺序的缺陷是必须按成员定义的顺序逐个初始化,不能间隔。而乱序的方式则很好的解决了这个问题,因为这种方式是按照成员名进行。
  eg:
   struct User oneUser = {
   .name = "Lucy",
   .id = 10,
   .home = "/home/Lucy"
   };
  
  3)乱序(C++风格)
  C++风格的乱序初始化方式跟C风格的一样,只是它更常用在C++代码里。
  eg:
   struct User oneUser = {
   name:"Lucy",
   id:10,
   home:"/home/Lucy"
   };
  
  不论是哪种方式,都允许只初始化部分成员;未被初始化的成员默认为0(指针类型的成员默认为NULL)。两种乱序初始化方法,即可以用在C代码中,也可以用在C++代码中。


vs2010运行环境配置


运行环境配置(项目属性中配置)
1 字符集可能不是unicode,而是多字节
2 运行程序前要包含程序所需要的包文件(.h)和需要的库文件(.lib)

———————以上两点做好后程序就知道从哪边得到库文件和头文件了,然后下面具体配置程序需要其中的哪些文件

3 在编写程序时,要引入相关的头文件(通过#include< .H>)
  引入库文件的方法有两种:通过Pragma comment(lib," .lib")或者通过在程序属性中附加依赖项上添加( .lib)属性



函数调用时的内存分配与回收

你能考虑到内存问题已经很不错了,不过C/C++指针以及内存管理问题一直都是新手迷茫的问题。
对于你的问题,我简单说几点吧:
1)在C/C++中内存有堆、栈之分(当然还有其他的,具体可以参考数据结构一类的书)。
2) 堆是用户管理的,C中一般用malloc()和free()管理,C++用new 和delete管理,而栈是计算机管理的不需要用户干预,但栈的大小有限,特别是嵌入式系统,在函数调用时,对于大的结构体一般传指针(32位系统固定4 个字节,其他系统等于AB(地址总线的宽度),参考微机原理一类的书),因为函数传参时,对于std_call,也需要压栈,大的结构体变量会使内存耗 完,设备立马会死翘翘,而该类故障没用语法错误,很难排查,一旦交付到用户手里,出了问题就麻烦了。
3)对于一个函数不会给函数体开辟一段内存, 函数原代码被编译器翻译成二进制后是放在代码段(CS段,可参考微机原理一类书籍),然后每一个函数会有一个入口地址,函数调用时会寻找这个地址,然后转 到这个地址执行函数内部代码,当然调用的地址也会保持,以便返回,这叫函数调用时上下文(context),中断也有类似的过程(可参考微机原理一类的书籍)。
4)函数内部的变量我们叫局部变量(local variable),如:
int foo(void)
{
int test = 0;// Local variable.
int *ptr = (int*) malloc(sizeof(int));// for C/C++ languange,
objiect *o_ptr = new objiect();// for C++ only
}
test变量为局部变量,函数执行时分配内存,分配到栈上,函数执行完释放,ptr 和o_ptr 自己所占有的空间,同test变量,但是二者指向的空间被分配到了堆上,该函数被执行完后不会被释放,而会造成内存泄露。


友元函数:

c++中引入了友元函数的概念,1)友元函数只能在类中申明,2)但是其又不属于类,因此在.cpp文件中实现其时,不需要添加类名::标记,3)友元函数的主要作用是能够在其函数中直接使用对象。成员变量的方法来访问对象的私有属性,提高了对对象的属性的访问效率(不需要通过类暴露的public函数访问其私有属性)


无名对象

c++中创建无名对象的方法是类名(),也就是直接调用构造函数产生的对象为无名对象。无名对象可用来作为函数的参数,同时可以给类对象赋值。

理解:vector<vector<double>  >vv(m,vector<double>(n));其中的vector<double>(n)就是无名对象,作为参数传递给二维vector作为其构造函数参数。


c++中对象是存放在栈中的还是存放在堆中的

c++中的对象既可以存放在栈中,也可以存放在堆中,直接定义的对象是放在栈中的,new出来的对象是放在堆中的。(不像java,对象都是new出来的)

 

C++/C语言中传递参数时,即使是传递的对象,只要不是引用传参,那么都是创建一个临时副本!!!这与JAVA不同,java传递的对象默认都是指针传递。



不同的数据类型对应的存储的数据的范围是不同的,byte存储范围是-128~127,而ubyte的范围是0~255,当存储数据时,使用的数据类型范围要大于存储的数据。

例如,ubyte a=255;这是正确的,但是byte b=255是错误的。虽然a、b的内存表示形式是一样的,都是11111111,但是他们的实际意义是不一样的,在进行数值计算时,计算机是根据实际意义进行计算的,这样就会产生错误。这样int c=a+1和int d=b+1的结果是不同的,c=256,而d=0,因为在解释b时,根据其byte的数据类型,11111111被解释为-1(计算机用补码表示).

(可以想象这种不平常的情况,在错误的情况下得到正确的结果,例如byte a=255; ubyte b=a;这时虽然a的实际意义是-1,但是b的值最终还是255。因为b=a直接将11111111赋值给b,b再根据ubyte类型解释11111111,还是是255)。虽然结果是正确的,但是这是一种很不好的编程方法,要杜绝,坚决操作变量的值在其范围内。


strlen执行时是从其参数所指地址开始,直到查询到‘\0'结束,记录字符串长度,而字符数组char s[]={'a','b','c'}中并没有存储'\0',所以其会继续向后查询,这样就产生了错误!!!!!


    int a=5;
    int b=3*a;//正确
    int c[a];//错误,数组大小必须是常量(编译时检查)。除非采用动态内存分配,话说这并不是分配数组。

string 是c++标准库里面其中一个,封装了对字符串的操作 
把string转换为char* 有3中方法: 
1。data 
如: 
string str="abc"; 
char *p=str.data(); 
2.c_str 
如:string str="gdfd"; 
    char *p=str.c_str(); 
3 copy 
比如 
string str="hello"; 
char p[40]; 
str.copy(p,5,0); //这里5,代表复制几个字符,0代表复制的位置
*(p+5)='\0'; //要手动加上结束符
cout < <p;


memset的使用!!!memset是用来设置字节内容的,其语法是将n个字节设置为指定的值!

原型:void *memset(void *buffer, int c, int count);
功能:把buffer所指内存区域的前count个字节设置成字符c。 注意一下,这里的count指的是字节数,而不是数组的元素个数。。
用您的例子来说明。。gcc编译器里面,int是占4个字节的,如果用memset进行填充,也就是说每个字节都为1为 00000001 00000001 00000001 00000001这个数,
10进制就是16843009。。一般来讲,memse只是用来填充单个字节的(比如char)或者填充0。。

qsort和sort的区别

qsort是定义在stdlib头文件中的,其时c语言中的排序算法,能够针对c语言中的数据类型进行排序,但是其是不能向后兼容的,当排序数据中存在stl中的数据结构时,其排序的正确性无法保证。

sort是stl中出现的方法,其是能够向前兼容的,同时能够处理stl类型数据的排序问题。

基本类型的const量的特点和iterator的特点

    const  char s2[4]="fef";
    char* p=(char*)(s2+1);//p是非const指针,所以通过p是可以修改其内容的!
    *p=65;//ok.
    *(++p)=67;//ok
    cout<<s2<<endl;//s2="fAC"
    (char)s2[0]='p';//ok, s2在上面是定义为const类型的,但是在此处被强制类型转换为非const类型了。因此也可以通过其对s2中的数据进行修改!
    cout<<s2<<endl;//s2="pAC";

    //上面的操作中,通过强制类型转换将数据类型改变从而可以修改数据,
    
    const string s="fewfwefw";
    string s1="fwefwe";
    //下面每一个函数的返回值类型都是固定的,左边必须与其完全对应。iterator没有强制类型转换!!
    string::const_iterator it=s.begin();
    string::iterator it1=s1.begin();
    string::const_iterator  it2=s1.begin();
    string::const_reverse_iterator it4=s1.rbegin();
    //但是iterator是无法进行强制类型转换的,所以无法使用const iterator来强制转换而修改内容。
    //iterator的类型是根据容器的类型和begin(),end(),cbegin(),cend(),rbegin(),rend()等函数共同决定的。创建变量时的类型必须与返回类型相同.
    //当容器为const类型时,返回必然是const型的。

区分“派生类对象”和“派生类”对基类成员的访问权限。

l  “派生类对象”对基类成员的访问权限:

   (1)对于公有继承,只有基类的公有成员可以被“派生类对象”访问,其他(保护和私有)成员不能被访问。

   (2)对于私有继承和保护继承,基类中所有成员都不能被“派生类对象”访问。

l  “派生类”对基类中成员的访问权限:

  (1)对于公有继承,基类中的公有成员和保护成员可以被“派生类”访问,而基类中的私有成员不能被访问。

  (2)对私有继承和保护继承,也是基类中的公有成员和保护成员可以被“派生类”访问,而基类中的私有成员不能被访问。

 



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值