c++随笔1
C语言和C++都有一个专为调试而准备的工具函数,就是 assert()函数
这个函数是在C语言的 assert.h 库文件里定义的,
所以包含到C++程序里我们用以下语句:#include <cassert>
作用是,如果它的条件返回错我,则终止程序的执行。
区分char *和char []:char *定义的是一个指向字符串的指针(注意:C语言中没有对应字符串的内置类型或者类类型),而char []就是C语言中的用来定义字符数组(注意:字符数组是不同于字符串,如果字符数组以'\0'结尾,那么可以视为字符串)。
char a[]在运行时赋值,值会从静态区赋值到函数的栈中,对它进行修改不会产生任何问题。char *a在编译时就确定了,a指向静态区中的值,没有赋值到函数栈中, 因此对指针的内容进行修改会产生错误
了解一下 一个由 C / C++ 编译的程序占用的内存分为以下几个部分:
1、栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
2、堆区(heap) — 一般由程序员分配释放, 若程序员不释放,程序结束时可能由 OS 回收。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。
3、全局区(静态区)(static)—,全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 - 程序结束后由系统释放 。
4、文字常量区 —常量字符串就是放在这里的。 程序结束后由系统释放
5、程序代码区—存放函数体的二进制代码。
关于头文件和源文件的分别
首先,我们可以将所有东西都放在一个.cpp文件内.
然后编译器就将这个.cpp编译成.obj,obj是什么东西?
就是编译单元了.一个程序,可以由一个编译单元组成,
也可以有多个编译单元组成. 如果你不想让你的源代码变得很难阅读的话,
就请使用多个编译单元吧.(一个函数不能放到两个编译单元里面,但两个以上
就可以分别放在一个单元,也就是cpp里面)
那么就是一个.cpp对应一个.obj,然后将所有的obj链接起来(通过一个叫链接器的程序),
组成一个.exe,也就是程序了.
如果一个.cpp要用到另一个.cpp定义的函数怎么办? 只需在这个.cpp种写上他的函数声明
就可以了.其余工作由链接器帮你完成,你可以随便调用该函数.
链接器将所有的obj连接起来,但是如果碰巧有相同的函数或外部变量怎么办?他如何识别?
一般来说是不能允许在同一个程序中,出现两个一样的函数名或外部变量名.
但是只得庆幸的是,c++可以通过一种叫做链接属性的关键字来限定,你这个函数是属于整个程序
公用的,还是只是在一个编译单元obj里面使用的.
这些关键字就是extern 和 static; extern是外部链接的意思,也就是除了这个单元,外部的单元也是能够访问这个函数的.static 是内部链接,自属于自己单元.
说了这么久,还没有说.h的作用呢?
其实没有.h也能很好的工作,但是当你发现一个外部链接的函数或外部变量,需要许多份
声明,因为c++这种语言,在使用函数和变量的时候,必须将他声明,为何要声明?声明之后才
知道他的规格,才能更好的发现不和规格的部分.你别妄想一个编译单元,会自动从另一个
编译单元那里得到什么信息,知道你是如何定义这个函数的.
所以说,只要使用到该函数的单元,就必须写一份声明在那个.cpp里面,这样是不是很麻烦,
而且,如果要修改,就必须一个一个修改.这真让人受不了.
.h就是为了解决这个问题而诞生,他包含了这些公共的东西.然后所有需要使用该函数的.cpp,只需要
用#include包含进去便可.以后需要修改,也只是修改一份内容.
请注意不要滥用.h,.h里面不要写代码,.h不是.cpp的仓库,什么都塞到里面.
如果在里面写代码,当其他.cpp包含他的时候,就会出现重复定义的情况,
比如将函数func(){printf};放到头文件a.h,里面还有一些a.cpp需要的声明等;
然后你发现b.cpp需要用到a.cpp里面的一个函数,就很高兴的将a.h包含进来.
注意,#include并不是什么申请指令,他就是将指定的文件的内容,原封不动的拷贝
进来.
这时候实际上a.cpp和b.cpp都有一个func()函数的定义.
如果这个函数是内部链接static的话,还好,浪费了一倍空间;
如果是extern,外部链接(这个是默认情况),那么根据在同一个程序内不可出现
同名函数的要求,连接器会毫不留情给你一个连接错误!
为了避免同一个文件被include多次
1 #ifndef方式
2 #pragma once方式
在能够支持这两种方式的编译器上,二者并没有太大的区别,但是两者仍然还是有一些细微的区别。
方式一:
#ifndef __SOMEFILE_H__
#define __SOMEFILE_H__
... ... // 一些声明语句
#endif
方式二:
#pragma once
... ... // 一些声明语句
#ifndef的方式依赖于宏名字不能冲突,这不光可以保证同一个文件不会被包含多次,也能保证内容完全相同的两个文件不会被不小心同时包含。当然,缺点就是如果不同头文件的宏名不小心“撞车”,可能就会导致头文件明明存在,编译器却硬说找不到声明的状况
#pragma once则由编译器提供保证:同一个文件不会被包含多次。注意这里所说的“同一个文件”是指物理上的一个文件,而不是指内容相同的两个文件。带来的好处是,你不必再费劲想个宏名了,当然也就不会出现宏名碰撞引发的奇怪问题。对应的缺点就是如果某个头文件有多份拷贝,本方法不能保证他们不被重复包含。当然,相比宏名碰撞引发的“找不到声明”的问题,重复包含更容易被发现并修正。
1 #include <stdio.h> 2 #include <stdlib.h> 3 int main() 4 { 5 printf("The program test print style!\n"); 6 /* 以十进制形式输出带符号整数(正数不输出符号) */ 7 printf("%d\n", 223); 8 printf("%d\n", -232); 9 printf("\n"); 10 /* 以八进制形式输出无符号整数(不输出前缀O) */ 11 printf("%o\n", 223); 12 printf("%o\n", -232); 13 printf("\n"); 14 /* 以十六进制形式输出无符号整数(不输出前缀OX) */ 15 printf("%x\n", 223); 16 printf("%x\n", -232); 17 printf("\n"); 18 /* 以十进制形式输出无符号整数 */ 19 printf("%u\n", 223); 20 printf("%u\n", -232); 21 printf("\n"); 22 /* 以小数形式输出单、双精度实数 */ 23 printf("%f\n", 223.11); 24 printf("%f\n", 232.11111111); 25 printf("%f\n", -223.11); 26 printf("%f\n", -232.11111111); 27 printf("\n"); 28 /* 以指数形式输出单、双精度实数 */ 29 printf("%e\n", 223.11); 30 printf("%e\n", 232.11111111); 31 printf("%e\n", -223.11); 32 printf("%e\n", -232.11111111); 33 printf("\n"); 34 /* 以%f%e中较短的输出宽度输出单、双精度实数 */ 35 printf("%g\n", 223.11); 36 printf("%g\n", 232.111111111111); 37 printf("%g\n", -223.11); 38 printf("%g\n", -232.111111111111); 39 printf("\n"); 40 /* 输出单个字符 */ 41 printf("%c\n", 'a'); 42 printf("%c\n", 97); //输出为 'a' 43 printf("\n"); 44 /* 输出单个字符 */ 45 printf("%s\n", "this is a test!"); 46 printf("%s\n", "2342o34uo23u"); 47 printf("\n"); 48 getch(); 49 }
assert的作用是现计算表达式 expression ,如果其值为假(即为0),那么它先向stderr打印一条出错信息,然后通过调用 abort 来终止程序运行
C编译器要求变量的声明需要放在作用域的开头,这里是函数的开始处。而C++编译器不要求,在哪用就在哪声明,也可以在开头声明好。所以,写C++代码是就比较放得开,什么时候需要什么时候声明就可以了。
sizeof(数组名)计算数组大小并不是每次都管用哦。sizeof(数组名)/sizeof(数组一个元素)也并不是每次都可以正确计算数组元素个数哦。
强行记忆学不好编程
1 #include <stdio.h> 2 #include <stdlib.h> 3 int main() 4 { 5 char url[] = "www.baidu.com"; 6 char bb[] = { 0,1,2,3,4 }; 7 int age[]= { 0,1,2,3,4 }; 8 int isizeurl = sizeof(url); 9 int isizebb = sizeof(bb); 10 int isizeage = sizeof(age); 11 printf("isizeurl:%d\n", isizeurl); 12 printf("isizebb:%d\n", isizebb); 13 printf("isizeage:%d", isizeage); 14 getchar(); 15 }
你可以看到,sizeof(数组名)是可以计算得到数组的字节大小的。这并不是全部的事实!
Linux系统下C程序开发详解:
1 .ii为扩展名的文件,是已经预处理过的C++源代码文件,也是中间代码文件。
2 .o为扩展名的文件,是编译后的目标文件,源文件生成的中间目标文件。
3 .s为扩展名的文件,是汇编语言源代码文件。
4 .S为扩展名的文件,是经过预编译的汇编语言源代码文件。
5 .o为扩展名的文件,是编译以后的程序目标文件(Object file),目标文件经过连接成可执行文件
注意 NULL 就是 NULL,它被宏定义为 0:
#define NULL 0
很多系统下除了有 NULL 外,还有 NUL
NUL 是 ASCII 码表的第一个字符,表示的是空字符,其 ASCII 码值为 0。其值虽然都为 0,但表示的意思 完全不一样。
函数宏中的#和##运算符
在函数宏中#可以实现由函数宏实参生成字符串常量,##实现了由函数宏实参生成标识符的一部分。(前者用于拼接字符串后者用于拼接标示符)看一下下边的示例:
假如希望在字符串中包含宏参数,ANSI C允许这样作,在类函数宏的替换部分,#符号用作一个预处理运算符,它可以把语言符号转化程字符串。例如,如果x是一个宏参量,那么#x可以把参数名转化成相应的字符串。该过程称为字符串化
1 #include<stdlib.h> 2 #include<stdio.h> 3 #define psqs(x) printf("the square of"#x "is %d.\n",(x)*(x)) 4 int main(void) { 5 int y = 4; 6 psqs(y); 7 psqs(2 + 4); 8 system("pause"); 9 }
第一次调用宏时使用“y”代替#x;第二次调用时用“2+4″代#x。
##运算符可以使用类函数宏的替换部分。另外,##还可以用于类对象宏的替换部分。这个运算符把两个语言符号组合成单个语言符号。例如:
#define XNAME(n) x##n
这样宏调用:
XNAME(4)
展开后:x4