c编程

1.内存管理


1.常识


  • size命令可以查看二进制可执行文件结构情况
  • 一个正在运行中的C编译程序占用内存分为:

    • 代码区 : 存放CPU执行的指令,该区域是共享的,只读
    • 初始化数据区 : 包含初始化的全局变量静态变量常量
    • 未初始化数据区 (BBS区) : 全局未初始化的变量,在运行时改变值
    • 堆区:向高地址扩展的不连续的空间,大,会产生碎片
    • 栈区:向低地址扩展的连续的空间,小,效率高
  • 依据:

    • 程序按流程执行,只要访问一次,数据要访问多次,单独开辟空间可以方便访问,节约空间
    • 临时数据放于栈区,生命周期短
    • 全局变量要一直访问
    • 堆区由用户自由分配,便于管理 
  • Linux数据类型大小参考/usr/include/limits.h

  • C结构体内不能有函数的代码,但可以有函数的指针
  • 二级指针没有初始化不能使用*运算符
  • invalid storage class for function ‘xxxxxxx’ 原因是少了个‘}’

  • 将using std namespace;放在特定的函数定义中,让函数能够使用std中的元素。

  • 函数原型只描述函数接口,描述发送给函数的信息和返回信息。
  • 函数定义中,包含了函数的代码

  • 对于const变量只能初始化,不能赋值

  • const能够指定类型。可将const用于更复杂的类型,和其他作用域规则,控制作用域
  • cin使用空白来定字符串的界,读取后将换行符留在输入队列中
  • getline()每次读取一行输入,随后丢弃换行符,
  • get() 将换行符保留在输入序列中。
  • cin.get()可以读取下一个字符(即使是换行符)
  • 当getline()或get()读取空行时,当get()读取到空行后将设置失效位(failbit),接下来的输入将会被阻断,使用cin.clear()恢复

  • 一个空的结构体或者类大小为1,如添加静态变量大小不变。

  • string类的一个友元函数getlin,用法string a; getline(cin, a);
  • 在不进行强制类型转换的时候,只能将定义枚举时使用的没去量赋给这种枚举的变量

  • 枚举的取值范围

enum bits {one = 1, two = 2, four = 4, eight = 8};
bits myflag;
myflag = bigs(6);
  • 取值范围,找到枚举量的最大值,找到大于这个最大值的,最小的2的幂,将它减1,就是取值范围的上限,例如bigstep的最大枚举值为101,则上限为127,
  • 找到枚举量的最小值,不小0,取下限为0,否则采用与上限方式相同的方式,但加上负号。
char ch;
cin >> ch;
while (ch != '#') {
    cout << ch;
    cin >> ch;
} //不能处理空格
cin.get(ch);
while(ch !='#') {
    cout << ch;
    cin.get(ch); //引用类型
}
  • 检测到EOF,cin将两位eofbit和failbit都设置为1,可以通过eof()来查看eofbit是否被设置,如果检测到EOF,则cin.eof()将返回true,否则为false
  • 如果eofbit或failbit被设置为1,则fail()函数范围true, 否则false
cin.get(ch);
while (cin.fail()== false) { //使用ctrl+z结束
    cout << ch;
    cin.getch(ch);
}
  • istream里面由一个可以将istream对象(如cin)转换为bool值的函数
    while (cin) //while cin is successful
while (cin.get(ch)) {
    cou t<< ch;
}
  • 如果数据类型本身不是指针,则可以将const数据或者非const数据的地址赋给指向const的指针,但只能将非const数据的地址赋给非const指针
  • 禁止将常量数组的地址赋给非常量指针。
  • 匹配函数时并不区分const和非const变量,

2.内存管理函数


2.1. malloc/free函数

#include <stdlib.h>

void *malloc(size_t size);
void free(void *ptr);

2.2. new/delete函数

static void* operator new(size_t sz);
static void operator delete(void* p);
  • 优点:new自动分配给对象内存空间大小,不适用sizeof运算符,返回正确的指针类型,不用强制转换类型,用构造函数给对象初始化
  • 相同点: 分配内存的时候,本身并没有对这块内存空间做清零等动作,一次要用memset()函数来初始化

2.3. realloc函数

void *realloc(void *ptr, size_t size);  //ptr为分配空间的内存指针,size为新配置的内存大小
//申请失败,原来空间有效,返回NULL。成功则释放原来的指针(不可用)
//所以有 ptr = realloc(ptr, size);

2.4. calloc函数
* 对malloc申请的内存进行初始化(memset(ptr, '\0', size);

2.5. alloca函数
* 用来在空间中分配size个字节的内存空间

extern void *alloca(size_t _size) _THROW;

3.常用内存调试工具


  • GDB, Binutil系列工具, Glibc提供的工具,MemWatch内存错误检测工具,valgrind工具
  • mcheck函数,可以检查出内存分配不匹配的情况
  • Valgrind检测:未初始化内存,释放内存,超过分配大小内存,非法访问,泄露,不匹配使用内存,地址重叠

2.文件操作


3.时间操作


  • 格林威治标准时间UTC,
  • 日历时间, 从一个标准时间点到此时经过的秒数
    #include <time.h>
    time_t time(time_t *tloc) //从1970-1-1-0,到现在的秒数
    //typedef long time_t
  • 时间转换
struct tm* gmtime(const time_t *timeep)
//将日历时间转为格林威治时间,保存至TM结构
struct tm* localtime(const time_t *timep)
//将日历时间转为本地时间
struct tm {
    int tm_sec; //秒
    int tm_min;
    int tm_hour;
    int tm_mday;   
    int tm_mon;
    int tm_year;   //  tm_year+1900 = 哪一年
    int tm_wday;  //本周第几日
    int tm_yday;  //本年第几日
    int tm_isdst;   //日光节约时间
};
char* asctime(const struct tm* tm);
//将tm格式的时间转为字符串,如:Sat Jul 30 08:43:03 2006
char* ctime(const time_t *timep)
//将日历时间转为本地时间的字符串形式
int gettimeofday(struct timeeval *tv, struct timezone * tz);
//获取从今日凌晨到现在的时间差,常用于激素三事件耗时

struct timeval {
    int tv_sec;  //秒,进制106
    int tv_usec; //微妙
};
unsigned int sleep(unsigned int seconds)
//是程序睡眠seconds秒
void usleep(unsigned long usec)
//是程序睡眠usec微秒

4. 异常


  • lvalue required as unary ‘&’ operand| 不能对一个值进行取址&操作

  • *

三 C语言中的异常处理

在C语言中异常处理一般有这么几种方式:

1.使用标准C库提供了abort()和exit()两个函数,它们可以强行终止程序的运行,其声明处于

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 double diva(double num1,double num2)         //两数相除函数 
 4 {
 5     double re;
 6     re=num1/num2;
 7     return re;
 8 }
 9 int main()
10 {
11    double a,b,result;
12  printf("请输入第一个数字:");
13   scanf("%lf",&a);
14   printf("请输入第二个数字:");
15   scanf("%lf",&b);
16   if(0==b)                                //如果除数为0终止程序 
17   exit(EXIT_FAILURE);
18 result=diva(a,b);
19    printf("相除的结果是: %.2lf\n",result);    
20 return 0;
21 }

其中exit的定义如下:

_CRTIMP void __cdecl __MINGW_NOTHROW exit (int) __MINGW_ATTRIB_NORETURN;
  • exit的函数原型:void exit(int)由此,我们也可以知道EXIT_FAILURE宏应该是一个整数,exit()函数的传递参数是两个宏,一个是刚才看到的EXIT_FAILURE,还有一个是EXIT_SUCCESS从字面就可以看出一个是出错后强制终止程序,而一个是程序正常结束。他们的定义是:
#define EXIT_SUCCESS 0
#define EXIT_FAILURE 1
  • 到此,当出现异常的时候,程序是终止了,但是我们并没有捕获到异常信息,要捕获异常信息,我们可以使用注册终止函数atexit(),它的原型是这样的:intatexit(atexit_t func);

  • 具体看如下程序:

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 void Exception(void)                           //注册终止函数,通过挂接到此函数,捕获异常信息 
 4 {
 5     printf("试图去除以一个为0的数字,出现异常!\n");
 6 }
 7 int main()
 8 { 
 9    double a,b,result;
10   printf("请输入第一个数字:");
11   scanf("%lf",&a);
12   printf("请输入第二个数字:");
13   scanf("%lf",&b);
14   if(0==b)                    //如果除数为0终止程序 ,并挂接到模拟异常捕获的注册函数
15   {
16       
17   atexit(Exception);                          
18   exit(EXIT_FAILURE);
19   } 
20    result=diva(a,b);
21    printf("相除的结果是: %.2lf\n",result);    
22 return 0;
23 }
  • 这里需要注意的是,atexit()函数总是被执行的,就算没有exit()函数,当程序结束时也会被执行。并且,可以挂接多个注册函数,按照堆栈结构进行执行。abort()函数与exit()函数类似,当出错时,能使得程序正常退出,这里就不多说了。

  • 使用assert()进行异常处理:

  • assert()是一个调试程序时经常使用的宏,切记,它不是一个函数,在程序运行时它计算括号内的表达式,如果表达式为FALSE (0), 程序将报告错误,并终止执行。如果表达式不为0,则继续执行后面的语句。这个宏通常原来判断程序中是否出现了明显非法的数据,如果出现了终止程序以免导致严重后果,同时也便于查找错误。

  • 另外需要注意的是:assert只有在Debug版本中才有效,如果编译为Release版本则被忽略。

  • 我们就前面的问题,使用assert断言进行异常终止操作:构造可能出现出错的断言表达式:assert(number!=0)这样,当除数为0的时候,表达式就为false,程序报告错误,并终止执行。

代码如下

#include <stdio.h>
#include <assert.h>
double diva(double num1,double num2)         //两数相除函数 
{
    double re;
    re=num1/num2;
    return re;
}
int main()
{
  printf("请输入第一个数字:");
  scanf("%lf",&a);
  printf("请输入第二个数字:");
  scanf("%lf",&b);
  assert(0!=b);                                //构造断言表达式,捕获预期异常错误
   result=diva(a,b);
   printf("相除的结果是: %.2lf\n",result);    
   return 0;
}
  • 使用errno全局变量,进行异常处理:

  • errno全局变量主要在调式中,当系统API函数发生异常的时候,将errno变量赋予一个整数值,根据查看这个值来推测出错的原因。

  • 其中的各个整数值都有一个相应的宏定义,表示不同的异常原因:

#define EPERM        1    /* Operation not permitted */
#define    ENOFILE        2    /* No such file or directory */
#define    ENOENT        2
#define    ESRCH        3    /* No such process */
#define    EINTR        4    /* Interrupted function call */
#define    EIO        5    /* Input/output error */
#define    ENXIO        6    /* No such device or address */
#define    E2BIG        7    /* Arg list too long */
#define    ENOEXEC        8    /* Exec format error */
#define    EBADF        9    /* Bad file descriptor */
#define    ECHILD        10    /* No child processes */
#define    EAGAIN        11    /* Resource temporarily unavailable */
#define    ENOMEM        12    /* Not enough space */
#define    EACCES        13    /* Permission denied */
#define    EFAULT        14    /* Bad address */
/* 15 - Unknown Error */
#define    EBUSY        16    /* strerror reports "Resource device" */
#define    EEXIST        17    /* File exists */
#define    EXDEV        18    /* Improper link (cross-device link?) */
#define    ENODEV        19    /* No such device */
#define    ENOTDIR        20    /* Not a directory */
#define    EISDIR        21    /* Is a directory */
#define    EINVAL        22    /* Invalid argument */
#define    ENFILE        23    /* Too many open files in system */
#define    EMFILE        24    /* Too many open files */
#define    ENOTTY        25    /* Inappropriate I/O control operation */
/* 26 - Unknown Error */
#define    EFBIG        27    /* File too large */
#define    ENOSPC        28    /* No space left on device */
#define    ESPIPE        29    /* Invalid seek (seek on a pipe?) */
#define    EROFS        30    /* Read-only file system */
#define    EMLINK        31    /* Too many links */
#define    EPIPE        32    /* Broken pipe */
#define    EDOM        33    /* Domain error (math functions) */
#define    ERANGE        34    /* Result too large (possibly too small) */
/* 35 - Unknown Error */
#define    EDEADLOCK    36    /* Resource deadlock avoided (non-Cyg) */
#define    EDEADLK        36
/* 37 - Unknown Error */
#define    ENAMETOOLONG    38    /* Filename too long (91 in Cyg?) */
#define    ENOLCK        39    /* No locks available (46 in Cyg?) */
#define    ENOSYS        40    /* Function not implemented (88 in Cyg?) */
#define    ENOTEMPTY    41    /* Directory not empty (90 in Cyg?) */
#define    EILSEQ        42    /* Illegal byte sequence */
  • 这里我们就不以前面的除数为0的例子来进行异常处理了,因为我不知道如何定义自己特定错误的errno,如果哪位知道,希望能给出方法。我以一个网上的例子来说明它的使用方法:
代码
#include <errno.h>  
#include <math.h>  
#include <stdio.h>  
int main(void)  
{  
errno = 0;  
if (NULL == fopen("d:\\1.txt", "rb"))  
{  
printf("%d", errno);  
}  
else  
{  
 printf("%d", errno);  
}  
return 0;  
}   
  • 这里试图打开一个d盘的文件,如果文件不存在,这是查看errno的值,结果是2
  • 当文件存在时,errno的值为初始值0。然后查看值为2的错误信息,在宏定义那边#define ENOFILE 2 /* No such file or directory */
    便知道错误的原因了。

  • 使用goto语句进行异常处理:

  • goto语句相信大家都很熟悉,是一个跳转语句,我们还是以除数为0的例子,来构造一个异常处理的例子

#include <stdio.h>
double diva(double num1,double num2)         //两数相除函数 
{
    double re;
    re=num1/num2;
    return re;
}
int main()
{
  int tag=0;
  double a,b,result;
  if(1==tag)
  {
      Throw:
    printf("除数为0,出现异常\n");
  }
   tag=1;
  printf("请输入第一个数字:");
  scanf("%lf",&a);
  printf("请输入第二个数字:");
  scanf("%lf",&b);
if(b==0)                                   //捕获异常(或许这么说并不恰当,暂且这么理解)
  goto Throw;                                //抛出异常 
  result=diva(a,b);
   printf("%d\n",errno);
   printf("相除的结果是: %.2lf\n",result);    

return 0;
}
  • 使用setjmp和longjmp进行异常捕获与处理:
  • setjmp和longjmp是非局部跳转,类似goto跳转作用,但是goto语句具有局限性,只能在局部进行跳转,当需要跳转到非一个函数内的地方时就需要用到setjmp和longjmp。setjmp函数用于保存程序的运行时的堆栈环境,接下来的其它地方,你可以通过调用longjmp函数来恢复先前被保存的程序堆栈环境。异常处理基本方法:

  • 使用setjmp设置一个跳转点,然后在程序其他地方调用longjmp跳转到该点(抛出异常).

代码如下所示:


#include <stdio.h>
#include <setjmp.h>
jmp_buf j;
void Exception(void)
{
   longjmp(j,1);
}
 double diva(double num1,double num2)         //两数相除函数
 {
    double re;
     re=num1/num2;
    return re;
}
 int main()
{
    double a,b,result;


   printf("请输入第一个数字:");
   scanf("%lf",&a);
   printf("请输入第二个数字:");
  if(setjmp(j)==0)
  {
   scanf("%lf",&b);
   if(0==b)
   Exception();
 result=diva(a,b);
    printf("相除的结果是: %.2lf\n",result);
  }
  else
  printf("试图除以一个为0的数字\n");
 return 0;
}

5. 其他

int i;
int a[N];
for (i=0; i<=N; i++)
a[i] = 0;

对于上述代码执行会陷入死循环,如果编译器是按照内存地址递减的方式来个变量分配内存,那么数组a之后的一个字实际上分配给了变量i.

由于操作符的优先级容易引发错误。
10和010的含义截然不同。
用单引号括起的一个字符代表一个整数,而双引号则代表一个指针

( * (void ( * ) ( ) ) 0 ) ( )

float *g( ) g是一个函数,该函数的返回值类型为指向浮点数的指针
float (*h)( ) h是一个函数指针,h所指向的函数的返回值为浮点数类型

float (*)( ) 表示一个只想返回值为浮点数类型的函数的指针

fp是一个函数指针,用(*fp) ()的方法可以调用fp 所指向的函数

void (* signal ( int , void ( * ) ( int ) ) ) ( int );

whiel ((c=getc(in)) != EOF)
putc(c, out);

注意作为语句结束标志的分号, 结构体定义完的分号
witch语句中的break

f()是函数调用,而f计算函数f的地址,却并不调用什么函数

c语言中,else始终与同一对括号内最近的未匹配的if结合,

int a[5], *p;
p = &a; 这句是非法的,因为&a是指向数组的指针,而p是指向整形数的指针,
p = a; 会把a中下表为0的元素的地址赋给p

int a[4][3];
int (*p)[3];
p = a;

a[i][j]相当于((a+i)+j)

int (*monthp)[31];
for (monthp = calendar: monthp < &calendar[12]; monthp++) {
int *dayp;
for (dayp = *monthp; dayp<&(*monthp)[31]; dayp++)
*dayp = 0;
}

C语言 强制要求声明数组大小为常量

char hello[] = “hello”;
数组作为参数传递时,实际是把数组第一个元素的地址作为参数传递给函数。
printf(“%s”, hello);与 printf(“%s”, &hello[0]);等效
将数组作为函数参数毫无意义,f(char s[]), 和f(char* s)等效

extern char *hello;与extern char hello[]完全不同;
如果一个指针参数并不实际代表一个数组,及时从技术上而言是正确的,用数组形式的记法经常会起到误导作用。

main(int argc, char* [] argv)与main(int argc, char** argv)完全等价

空指针并非空字符串

讲一个整数转换为指针,最后的结果取决于具体的C编译器实现,0除外,也就是NULL, 它不等于任何有效指针

char* p = (char*)0;
printf(p); 不会打印任何东西
printf(“%s”, p); 会打印(null)
以上都在codeblock 中测试

首先考虑最简单的问题,然后外推
仔细计算边界

用第一个入界点和第一个出界点来表示一个范围,之差为数据个数,(不对称边界)

define N 1024

static char buffer[N];
static char *bufptr;

void bufwrite(char* p, int n) {
while (–n >= 0) {
if (bufptr == &buffer[N])
flushbuffer();
*bufptr++ = *p++;
}
}
数组buffer元素下标从0到N-1, 根本不能到N,用if (bufptr == &buffer[N]) 代替了if (buffptr > &buffer[N-1]), 坚持了’不对称边界’,
再次我们并没有引用一个不存在的元素,而是引用了一个地址,引用元素就非法了。

提高上面函数的性能:
void memcpy(char* dest, const char* source, int k) { //一次移动k个字符
while (–k >= 0)
*dest++ = *source++;
}

void bufwrite(char* p, int n) {
while (n > 0) {
int k, rem;
if (bufptr == &buffer[N])
flushbuffer();
rem = N-(bufptr - buffer); //另一种计算rem的方法:(buffer+N)-bufptr
k = n>rem? rem:n;
memcpy(bufptr, p, k);
bufptr += k;
n -= k;
}
}
避免了判断两次,转移一个字符。

大多数C语言实现都通过函数main的返回值来告诉操作系统函数执行成功或失败,因此main函数的返回值还是很有必要的
对main函数如果没有显式的指明返回值类型,默认为整形

当发生溢出时,任何关于结果的假设都不靠谱。
如 if (a+b < 0)
complain();
一种正确的方法是:将a, b强制转换为无符号整数
if ( (unsigned)a + (unsigned)b > INT_MAX)
complain();

在《limits.h>中定义了INT_MAX
另一种方法为:
if(a > INT_MAX - b)
complain();

程序的输出可能包含若干页的的整数,每页NCOLS列,每列NROWS个元素,程序生成的整数是按照列来分布的,而不是按行分布的。
这些整数进入缓冲区的顺序与出缓冲区的顺序不一致,按列接受数值,按行打印数值。

define BUFSIZE (NROWS*(NCOLS-1))

static int buffer[BUFSIZE];

/*
printnum在本页的当前位置打印一个数值
printnl打印一个换行符
printpage打印一个分页符
*/

void print(int n) {
if (bufptr == &buffer[BUFSIZE]) {
static int row = 0;
int *p;
for (p = buffer+row; p < bufptr; p += NROWS) {
printnum(*p);
}
printnum(n); //打印当前行的最后一个元素
printnl(); //打印换行符
if (++row == NROWS) {
printpage();
row = 0; //重置当前行序号
bufptr = buffer; //重置指针bufptr
}
} else {
*bufptr++ = n;
}
}

void flush() {
int row;
int k = bufptr - buffer; //计算缓冲区中剩余项的数目
if (k > NROWS) {
K = NROWS;
}
if (k > 0) {
for (row = 0; row < k; row++) {
int *p;
for (p = buffer+row; p

define ENDIANNESS ((char)endian_test.mylong)

Linux 的内核作者们仅仅用一个union 变量和一个简单的宏定义就实现了一大段代码同样的功能!由以上一段代码我们可以深刻领会到Linux 源代码的精妙之处!(如果ENDIANNESS=’l’表示系统为little endian,
为’b’表示big endian )

试题二:假设网络节点A 和网络节点B 中的通信协议涉及四类报文,报文格式为“报文类型字段+报文内容的结构体”,四个报文内容的结构体类型分别为STRUCTTYPE1~ STRUCTTYPE4,请编写程序以最简单的方式组
织一个统一的报文数据结构。
分析:
报文的格式为“报文类型+报文内容的结构体”,在真实的通信中,每次只能发四类报文中的一种,我们可以将四类报文的结构体组织为一个union(共享一段内存,但每次有效的只是一种),然后和报文类型字段统一组织成一个报文数据结构。
解答:
根据上述分析,我们很自然地得出如下答案:
typedef unsigned char BYTE;
//报文内容联合体
typedef union tagPacketContent
{
STRUCTTYPE1 pkt1;
STRUCTTYPE2 pkt2;
STRUCTTYPE3 pkt1;
STRUCTTYPE4 pkt2;
}PacketContent;
//统一的报文数据结构
typedef struct tagPacket
{
BYTE pktType;
PacketContent pktContent;
}Packet;

关键字:
数据类型(17):char, short, int, long, float, double, register, volatile, auto, extern, static, struct, enum, union, const, signed, unsigned
语句控制(11):if, else, do, while, for, goto, switch, case, default, break, continue
其他(4):sizeof, typedef, void, return

定义就是编译器创建一个对象,为这个对象分配一块内存,并取一个名字(变量名,对象名), 一个变量或对象在一定的区域内只能被定义一次。
声明就是说明代码用到的对象或变量在其他地方已经定义过,可以出现多次。
声明是告诉编译器,这个名字我预定了,别的地方不能用它来作为变量名
如:void fun(int i, char c)

register尽可能把变量放在CPU内部寄存器中而不是内存以提高效率。该变量必须是能被CPU其存期所接受的类型,意味着register变量必须是一个单个的值,并且其长度应小于或等于整型的长度。而且register变量可能不存在内存中,所以不能用取址运算符来获取register变量的地址。

静态全局变量,作用域仅限于变量被定义的文件中。
被static修饰的变量总是存在内存的静态去。

当自定义结构类型时使用_st后缀,当自定义结构数据类型为指针类型时使用_pst后缀。

一个函数名禁止 被用于其他之处(不是函数调用)

所有宏定义,枚举常数,只读变量全用大写字母命名用下划线分割单词。

局部变量中可以用通用的命名方式,仅限于n, i, j等作为循环变量使用。

定义变量时一定要初始化。

sizeof在计算变量所占空间是可以省略括号,但是计算数据类型时不能省略。

global—–>g
static function/static variable(native)—–>n
function static variable—–>f
auto —–>a
bit —–>bt
bool —–>b
char —–>c
int —–>i
short —–>s
long —–>l
unsigned—–>u
double —–>d
float —–>f
pointer —–>p
void —–> v
enum/strucct /uinion —–>st
function/point —–>fp
array of —–>_a
typedef enum/struct/union —–>_st/_pst

int a[100];
sizeof(a) = 400
sizeof(&a) = 4

include

include

define M 3 //宏常量

const int N=5; // 此时并未将N 放入内存中

int i=N; //此时为N 分配内存,以后不再分配!
int I=M; //预编译期间进行宏替换,分配内存
int j=N; //没有为N分配内存
int J=M; //再进行宏替换,又一次分配内存!

int main() {
const int* p = new int;
int q = 2, a = 3;
p = &q;
cout << p << ” ” << *p << endl;
q = 3;
cout << p << ” ” << *p << endl;
q = 4;
cout << p << ” ” << *p << endl;
p = &a; //可以改变p的指向
cout << p << ” ” << *p << endl;
return 0;
}

const int *p; // p 可变,p 指向的对象不可变
int const *p; // p 可变,p 指向的对象不可变
int *const p; // p 不可变,p 指向的对象可变
const int *const p; // 指针p 和 p 指向的对象都不可变

近水楼台先得月

const int *p; //const 修饰*p,p 是指针, *p 是指针指向的对象,不可变
int const *p; //const 修饰*p,p 是指针, *p 是指针指向的对象,不可变
int *const p; //const 修饰p, p 不可变,p 指向的对象可变
const int *const p; // 前一个const 修饰 *p,后一个const 修饰p,指针 p 和p 指向的对象都不可变

volatile 关键字和const 一样是一种类型修饰符,用它修饰的变量表示可以被某些编译器
未知的因素更改,比如操作系统、硬件或者其它线程等。遇到这个关键字声明的变量,编
译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。

空结构体大小为1, 结构体大小为不包含柔性数组的内存大小。

大端模式(Big_endian):字数据的高字节存储在低地址中,而字数据的低字节则存放在高地址中。
小端模式(Little_endian):字数据的高字节存储在高地址中,而字数据的低字节则存放低地址中。

enum是对一个变量取值范围的限定。

对常量不能用sizeof, 如sizeof(3), 会发生溢出.
enum的长度为4

#define PCHAR char*
PCHAR p3, p4; //p3是指针,p4是char型数据

typedef int b[5]
b c; //c为一个有五个元素的数组

。逻辑运算符||两边的条件只要有一个为真,其结果就为真;只要有一个结果为假,其结果就为假。if((++i>0)||(++j>0))语句中,先计算(++i>0),发现其结果为真,后面的(++j>0)便不再计算。

交换两个变量的值
a^=b;b^=a;a^=b;

‘<<’ 和 ‘>>’
对于有符号数,在右移时,符号位将随同移动。当为正数时, 最高位补0;而为负数时,符号位为1,最高位是补0 或是补1 取决于编译系统的规定。Turbo C 和很多系统规定为补1。

左移和右移的位数不能大于数据
的长度,不能小于 0。

\ddd 1~3位八进制数所表达的字符
\xhh 1~2为十六进制数所代表的字符

++运算符,–运算符,只需要一个操作数,单操作数只能是变量,不能是常量或者表达式。

.的优先级高于*, ->操作符用于消除这个问题
[]高于*
函数()高于*
==和!=高于赋值符
==和!=高于位操作
算术运算符高于唯一运算符

LINE 表示正在编译的文件的行号
FILE 表示正在编译的文件的名字
DATE 表示编译时刻的日期字符串,如:25Dec 2007
TIME表示编译时刻的时间字符串 , 如:12:30:55
STDC 判断该文件是不是定义成标准c程序

反斜杠作为连接符时,在本行其后面不能有任何字符

不能用宏定义来代替注释符,因为注释先于预处理指令被处理。

宏定义,整数值常缺省类型为unsigned int。通过加后缀UL可以使其为unsigned long, 如:#define SIZE 10UL

宏函数被调用时是以实参代换形参,而不是值传递。

在定义好的宏函数SUM(X),在使用时SUM和(X)之间留空格是不影响的,会被编译器自动忽略。

#undef是用来撤销宏定义的。

在编译程序时,只要遇到#error就会生成一个编译错误提示消息并停止编译。

#line是用来改变当前行数和文件名称

#pragma 指令用来设定编译器的状态和指定编译器完成一些特定的动作。
#pragma message(“…”)
#pragma code_seg()能够设置程序中函数代码存放的代码段,。

#pragma once 只要在文件的开头使用,就能保证头文件被编译一次。考虑到兼容性,就没有太多使用。

#pragma hdrstop 表示编译头文件到此为止,可加快链接速度,
#pragam startup 指定编译优先级
如果使用了#pragma package(smart_init), BCB就会根据优先级大小先后编译。

#pragma resource
#pragma “.dfm” 表示把。dfm文件加入工程。

#pragma warning( disable : 4507 34; once : 4385; error : 164 )
等价于:
#pragma warning(disable:4507 34) // 不显示4507 和 34 号警告信息
#pragma warning(once:4385) // 4385 号警告信息仅报告一次
#pragma warning(error:164) // 把164 号警告信息作为一个错误。
同时这个 pragma warning 也支持如下格式:
#pragma warning( push [ ,n ] )
#pragma warning( pop )
这里n 代表一个警告等级 (1—4)。
#pragma warning( push ) 保存所有警告信息的现有的警告状态。
#pragma warning( push, n) 保存所有警告信息的现有的警告状态,并且把全局警告
等级设定为 n。
#pragma warning( pop ) 向栈中弹出最后一个警告信息,在入栈和出栈之间所作的
一切改动取消。例如:
#pragma warning( push )
#pragma warning( disable : 4705 )
#pragma warning( disable : 4706 )
#pragma warning( disable : 4707 )
//…….
#pragma warning( pop )

#pragma comment(…)
该指令将一个注释记录放入一个对象文件或可执行文件中。
常用的lib 关键字,可以帮我们连入一个库文件。比如:
#pragma comment(lib, “user32.lib”)
该指令用来将 user32.lib 库文件加入到本工程中。
linker:将一个链接选项放入目标文件中 ,你可以使用这个指令来代替由命令行传入的或
者在开发环境中设置的链接选项 ,你可以指定/include 选项来强制包含某个对象 ,例如:
#pragma comment(linker, “/include:__mySymbol”)

原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;然而,对齐的内存访问仅需要一次访问。所以要内存对齐。
#pragma pack(n) 编译器将按照n字节对齐
#pragma pack() 编译器将取消自定义字节对齐方式

首先,每个成员分别按自己的方式对齐,并能最小化长度。
其次,复杂类型(如结构)的默认对齐方式是它最长的成员的对齐方式,这样在成员是复杂
类型时,可以最小化长度。
然后,对齐后的长度必须是成员中最大的对齐参数的整数倍,这样在处理数组时可以保
证每一项都边界对齐。

补充一下,对于数组,比如:char a[3];它的对齐方式和分别写3 个char 是一样的.也就是说
它还是按1 个字节对齐.如果写: typedef char Array3[3];Array3 这种类型的对齐方式还是按1
个字节对齐,而不是按它的长度。
但是不论类型是什么,对齐的边界一定是1,2,4,8,16,32,64….中的一个。
另外,注意别的#pragma pack 的其他用法:
#pragma pack(push) //保存当前对其方式到packing stack
#pragma pack(push,n) 等效于
#pragma pack(push)
#pragma pack(n) //n=1,2,4,8,16 保存当前对齐方式,设置按n 字节对齐
#pragma pack(pop) //packing stack 出栈,并将对其方式设置为出栈的对齐方

##运算符可以用于红函数的替换部分,这个运算符可以把两个语言符号组合成单个语言符号
如:#define XNAME(N) x##N
XNAME(8),就会变成x8

int *p;
*p = NULL;
由于p是一个指针,可能指向一个非法地址,所以上面的程序会出错。

NUL是ASII码表的第一个字符,表示空字符。

int p = (int)0x12ff7c
*p = 0x100;
往内存0x12ff7c中写入数据0x100

关键字sizeof求值实在编译的时候。

数组a作为右值其意义与&a[0]一样,代表的是数组首元素的首地址,而不是数组的首地址。

数组名不能作为左值。
但是:int a[5];
*a = 2;
cout << a[0] << endl; //2
cout << a << endl; //2
//cout <

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值