static在C和C++里各代表什么含义?

static关键字有三种使用方式,其中前两种只指在C语言中使用,第三种在C++中使用。

1. 局部静态变量(C)

2. 外部静态变量/函数(C)

3. 静态数据成员/成员函数(C++)

一、 局部静态变量

局部变量按照存储形式可以分为三种,分别是auto、static、register。

与auto类型(普通)局部变量相比,static有三点不同:

1. 存储空间分配不同

auto类型分配在栈上,属于动态存储类别,占动态存储空间,函数调用结束后自动释放;

static类型分配在静态存储区,在程序整个运行期间都不释放;

两者作用域相同,并且生存期不同。

2. static局部变量在初次运行时进行初始化工作,且只初始化一次。

3. 对于局部静态变量,如果不赋初值,编译期会自动赋初值0或者空;

auto类型的初值是不确定的。

对于C++的类对象例外,class的对象实例如果不初始化,则会自动调用默认构造函数,不管是不是static类型。

特点:static局部变量的“记忆性”与生存期的“全局性”

所谓“记忆性”是指在两次函数调用时,在第二次调用进入时,能保持第一次调用退出时的值。

利用生存期的”全局性“改善return a pointer / reference to a local object的问题,local object的问题在于退出函数时,生存期就结束,局部变量就会被销毁;利用static就可以延长局部变量的生存期。

注意事项:


1. “记忆性”是程序运行很重要的一点就是可重复性,而static变量的“记忆性”破坏了可重复性,造成不同时刻同一函数的运行结果不同。

2. “生存期”全局性和唯一性。 普通的局部变量在栈上分配空间,因此每次调用函数时,分配的空间都可能不一样,而static具有全局唯一性的特点,每次调用时都指向同一块内存,这就造成一个很重要的问题---不可重入性!!!

在多线程或者递归程序中要特别注意。

二、 外部静态变量/函数

在C中static的第二种含义:用来表示不能被其它文件访问的全局变量和函数。

此处static的含义是指对函数的作用域仅仅局限于本文件(所以又称为内部函数)。

注意:对于外部(全局)变量,不论是否有static限制,它的存储区域都是在静态存储区,生存期都是全局的,此时的static只是起作用域限制作用,限制作用域在本文件内部。

使用内部函数的好处是:不同的人编写不同的函数时,不用担心函数同名问题。

三、 静态数据成员/成员函数(C++特有)


C++重用了这个关键字,它表示属于一个类而不是属于此类的任何特定的对象的变量和函数。

静态类成员包括静态数据成员和静态函数成员。

1. 静态数据成员

类体中的数据成员的声明前加上static关键字,该数据成员就成为了该类的静态数据成员。和其他数据成员一样,静态数据成员也遵守public/protected/private访问规则。同时静态数据成员还具有以下特点。

1) 静态数据成员的定义

静态数据成员实际上是类域中的全局变量。所以,静态数据成员的定义(初始化)不应该被放在头文件中

注:不要试图在头文件中定义(初始化)静态数据成员。在大多数情况下,这会引起重复定义。即使加上#ifndef  #define  #endif或者#pragma once也不行。

2) 静态数据成员被类的所有对象所共享,包括该类的派生类的对象。

3) 静态数据成员可以成为成员函数的可选参数,而普通数据成员则不可以


 4)★静态数据成员的类型可以是所属类的类型,而普通数据成员则不可以。普通数据成员的只能声明为所属类类型的指针或引用。静态数据成员的值在const成员函数中可以被合法的改变。


2 静态成员函数    


1).静态成员函数的地址可用普通函数指针储存,而普通成员函数地址需要用类成员函数指针来储存。

2).静态成员函数不可以调用类的非静态成员。因为静态成员函数不含this指针。  

3).静态成员函数不可以同时声明为   virtual、const、volatile函数。


最后要说的一点是,静态成员是可以独立访问的,也就是说,无须创建任何对象实例就可以访问。

const在C/C++里有什么意思?

在C中const的用法:

  1、在定义变量时使用(const常量在定义后不能被修改,故在定义时一定要进行初始化操作)

  A:变量是个常变量: int const a=10;

  const int a=10;

  B:指针为指向常数的指针,(指针本身的值可以改变)

  const int *cur=&b;

  C :指针本身值不可变,但指向的内容可以变

  int * const cur=&b;

  D :指针为指向常数的常指针,即指针本身及指针指向的内容都不可变

  const int * const cur =&b;

  E: 说明引用为常数引用,即不能改变引用的值

  const int& cur = 10;

  2、在定义函数时使用

  A 作为参数使用,说明函数体内是不能修改该参数的:


  int fun(const int i);


B 返回值使用,说明函数的返回值是不能被修改的:


  const int fun();


C 在函数中使用const,情况与定义变量的情况基本一致:


在C++中const用法:

 A : const类成员变量

  在对象构造期间允许被初始化并在以后不允许被改变。const类成员和一般的const变量不同,const类成员是对于每个对象而言,它在对象构造期间被初始化,在这个对象生命期间不允许被改变。

 class A

  {

  …

  const int nValue; //成员常量不能被修改

  …

  A(int x): nValue(x) { } ; //只能在初始化列表中赋值

  }

B: const成员函数

  const 成员函数不允许在此函数体内对此函数对应的类的所有成员变量进行修改,这样可以提高程序的健壮性。一般写在函数的最后来修饰

  class A

  {

  …

  void function()const; //常成员函数, 它不改变对象的成员变量.

  //也不能调用类中任何非const成员函数。

}

C: const修饰类对象/对象指针/对象引用

  const修饰类对象表示该对象为常量对象,其中的任何成员都不能被修改。对于对象指针和对象引用也是一样。

  const修饰的对象,该对象的任何非const成员函数都不能被调用,因为任何非const成员函数会有修改成员变量的企图。

  class AAA

  {

  void func1();

   void func2() const;

  }

  const AAA aObj;

  aObj.func1(); //×

  aObj.func2(); //正确

  const AAA* aObj = new AAA();

  aObj-> func1(); //×

  aObj-> func2(); //正确

  const在C和C++中最大的不同是,在C中,const默认具有外部链接,而C++中则是内部链接。

  所以当你只在定义const常量的文件中使用该常量时,c++不给你的const常量分配空间,此时const int c = 0;

  相当于#define c 0;而在C中,它会给每个const 常量分配内存空间。

volatile的作用?

★const和define的区别:


1.Const常量有数据类型,而宏常量没有数据类型,编译器可以对前者进行类型安全的检查,对后者只进行字符替换,没有类型安全的检查,并且在字符替换中产生意料不到的错误;


2.有些集成的调试工具可以对const常量进行调试,但是不能对宏常量进行调试;


★volatile


volatile 影响编译器编译的结果,指出volatile 变量是随时可能发生变化的,与volatile变量有关的运算,不要进行编译优化,以免出错,(VC++ 在产生release版可执行码时会进行编译优化,加volatile关键字的变量有关的运算,将不进行编译优化).


例如:


volatile int i=10;


int j = i;


 ...


int k = i;


volatile 告诉编译器i是随时可能发生变化的,每次使用它的时候必须从i的地址中读取,因而编译器生成的可执行码会重新从i的地址读取数据放在k中。 而 优化做法是,由于编译器发现两次从i读数据的代码之间的代码没有对i进行过操作,它会自动把上次读的数据放在k中。而不是重新从i里面读。这样以来,如果 i是一个寄存器变量或者表示一个端口数据就容易出错,所以说volatile可以保证对特殊地址的稳定访问,不会出错。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。


★struct和class的区别:


Struct是public,class是private;


“class”这个关键字还用于定义模板参数,就像“typename”。但关键字“struct”不用于定义模板参数。


struct更适合看成是一个数据结构的实现体,class更适合看成是一个对象的实现体。


★enum


Enum 枚举名 {枚举元素列表}


例如:enum {sun,mon,tus,wed,thu,fri,sat}workday,weekend;


C 编译对枚举类型里的枚举元素按常量处理,称为枚举常量;每一个枚举元素都代表了一个整数,按定义的顺序默认它们的值为0,1,2,3,4,5,6,……,也可以人为的指定值,但后面的元素必须是依次加1;枚举元素也可以来用作比较;


★union 共用体或联合体


 union用来维护足够的空间来放置多个数据成员中的一种,在union中所有的数据共用一个存储空间,同时间只能存储其中一个数据成员,也只能用其中的这个数据成员,不能同时被用,所以它起到了一个压缩空间的作用,所有的成员具有相同的起始地址;一个union只配置一个足够大的空间来容纳最大长度的数据的大小。


★inline


 内联函数和普通函数相比加快了程序的运行速度,因为不需要中断调用,在编译的时候内联函数可以直接被镶嵌到目标代码中,而减少普通函数调用时的资源消耗;


注意事项:在内联函数内不允许使用循环语句和开关语句,否则按非内联函数处理;内联函数的定义必须出现在内联函数第一次调用之前;


★register 寄存器


速度是最快的!数据从内存中拿出来就放在寄存器中,然后cpu从寄存器中拿数据;


★数组


一维数组:int a[10] 该数组有十个元素,下标为0—9;


数组名是数组首元素的地址,及a等价于&a[0];


二维数组:


通过指针引用二维数组:


*(a[i]+j)或*(*(a+i)+j)是a[i][j]的值;


a[0]+0,a[0]+1分别是a[0][0],a[0][1]元素的地址(即&a[0][0],&a[0][1]);


*(a[0]+j)指向的是a[0][j]的值;*(a+j)指的是&a[j];


在指向行的指针前加一个*,就转换为指向列的指针;在指向列的指针前加&就成为指向行的指针。


★指针与数组的对比

1.修改内容

示例 7-3-1 中,字符数组 a 的容量是 6 个字符,其内容为 hello\0 a 的内容可以改
变,如 a[0]= ‘X’。指针 p 指向常量字符串“ world”(位于静态存储区,内容为 world\0),
常量字符串的内容是不可以被修改的。从语法上看,编译器并不觉得语句 p[0]= ‘X’有什
么不妥,但是该语句企图修改常量字符串的内容而导致运行错误。
char a[] = hello;
a[0] = X;
cout << a << endl;
char *p = world; // 注意 p 指向常量字符串
p[0] = X; // 编译器不能发现该错误
cout << p << endl;

示例 7-3-1 修改数组和指针的内容

2. 内容复制与比较

不能对数组名进行直接复制与比较。示例 7-3-2 中,若想把数组 a 的内容复制给数
组 b,不能用语句 b = a ,否则将产生编译错误。应该用标准库函数 strcpy 进行复制。
同理, 比较 b 和 a 的内容是否相同, 不能用 if(b==a) 来判断, 应该用标准库函数 strcmp
进行比较。
语句 p = a 并不能把 a 的内容复制指针 p,而是把 a 的地址赋给了 p。要想复制 a
的内容,可以先用库函数 malloc 为 p 申请一块容量为 strlen(a)+1 个字符的内存,再
用 strcpy 进行字符串复制。同理,语句 if(p==a) 比较的不是内容而是地址,应该用库
函数 strcmp 来比较。
// 数组
char a[] = "hello";
char b[10];
strcpy(b, a); // 不能用 b = a;
if(strcmp(b, a) == 0) // 不能用 if (b == a)

// 指针
int len = strlen(a);
char *p = (char *)malloc(sizeof(char)*(len+1));
strcpy(p,a); // 不要用 p = a;
if(strcmp(p, a) == 0) // 不要用 if (p == a)

示例 7-3-2 数组和指针的内容复制与比较
高质量 C++/C 编程指南, v 1.0
2001 Page 47 of 101

3.计算内存容量

用运算符 sizeof 可以计算出数组的容量(字节数)。示例 7-3-3( a)中, sizeof(a)
的值是 12(注意别忘了\0)。指针 p 指向 a,但是 sizeof(p)的值却是 4。这是因为
sizeof(p)得到的是一个指针变量的字节数,相当于 sizeof(char*),而不是 p 所指的内
存容量。 C++/C 语言没有办法知道指针所指的内存容量,除非在申请内存时记住它。
注意当数组作为函数的参数进行传递时,该数组自动退化为同类型的指针。示例
7-3-3( b)中,不论数组 a 的容量是多少, sizeof(a)始终等于 sizeof(char *)。
char a[] = "hello world";
char *p = a;
cout<< sizeof(a) << endl; // 12 字节
cout<< sizeof(p) << endl; // 4 字节
示例 7-3-3 a) 计算数组和指针的内存容量
void Func(char a[100])
{

cout<< sizeof(a) << endl; // 4 字节而不是


★指针与引用的区别


1.非空区别:在任何情况下不能使用指向控制的引用,一个引用必须是指向某些对象,因此你如果使用一个变量并让它指向一个对象,但是该变量在某些时候也不指向任何对象,这是你应该把变量声明为指针,因为这样你可以赋空值给该变量,相反如果变量指向一个对象,例如你的设计不允许变量为空,这时你就可以把变量声明为引用。使用引用的代码效率肯定比使用指针的效率高。


2.合法性区别


在使用引用之前不需要检测其合法性,相反,指针则应该接受检查,防止其为空;


3.可修改区别


指针可以被重新赋值指向另一个不同的对象,但引用总是指向在初始化时被指定的对象,以后不能改变,但是指定的对象其内容可以改变。


④应用区别


指针:一是你考虑到存在不指向任何对象的可能;二是你需要能够在不同的时刻指向不同的对象;如果总是指向一个对象,并且一单指向一个对象后就不会改变,那么你应该使用引用。


★字节对齐


在默认情况下,为了方便底结构体内元素的访问和管理,当结构体内的元素的长度都小于处理器的位数是,便以结构体里面最长的数据元素对齐单位,也就是说结构体的长度一定是最长数据元素的整数倍,如果结构体内存在长度大于处理器位数的元素,那么就以处理器的位数为对齐单位。但是结构体内类型相同的连续元素将在连续的空间内,和数组一样。

1.整体空间是占用空间最大的成员的类型所占字节的整倍数;

2.数据对其原则:内存按结构成员的先后顺序排列,当排到该成员变量时,其前面已摆放的空间大小必须是该成员类型大小的整倍数,如果不够则补齐,以此向后类推;

★Malloc/free和new/delete的区别和联系


1.它们都是动态管理内存的入口;


2.malloc/free是c/c++标准的库函数,new/delete是c++操作符;


3.malloc/free只是动态分配内存空间/释放空间,而 new/delete除了分配空间还会调用构造函数和析构函数进行初始化与清理;


④malloc/free需要手动计算类型大小且返回值void*,new/delete可自己计算类型的大小,返回对应类型的指针。


★文件管理


打开文件:fopen(文件名,打开方式) 打开方式常见的有“r(读)”“w(写)“;


关闭文件:fclose(文件指针)


例如:FILE*fp=fopen(file,”r”) fp就为文件指针;


if(fp==NULL)打开失败;如果以写(w)的方式打开,不存在文件file的话,就会重建file;


fgetc(fp)// 从fp所指向的文件中读取一个字符;


fputc(ch,fp)把字符ch输出到fp所指向的文件中;


char*fgets(char*str,int n,FILE*p)//从文件中读一个长度为n的字符串存放到str中;


int fputs(char*str,FILE*p)//将str中的字符串输出到fp所指向的文件中;


fread(buffer,size,count,fp);


fwrite(buffer,size,count,fp)


Buffer指的是存放从文件中读取的数据的存储区的地址,对于fwrite是要把此地址开始的存储区中的数据文件向文件输出;


size:要读写的字节数;


count:要读写多少个数据项;


fp:FILE类型的指针。


文件位置标记的定位:fseek(文件类型指针,位移量,起始点);


文件起始位置: SEEK_SET 用数字0代表;


文件当前位置: SEEK_CUR 用数字1代表;


文件末位位置: SEEK_END 用数字2代表;


fseek(fp,0,SEEK_SET) // fseek(fp,0,0)文件开始位置;


fseek(fp,100L,0)将文件位置标记向前移动到离文件开头100个字节处;


fseek(fp,50L,0)将文件位置标记向前移到离当前位置50个字节处;


fseek(fp,-50L,2)将文件位置标记从文件末尾处向后退10个字节;


feof:int feof(FILE*fp) 检查文件是否结束,遇文件结束符返回非零值,否则返回0;


eof:检查文件是否结束,遇文件结束,返回1,否则返回0。