[C/C++基础--笔试突击] 5.C预处理器、作用域、static、const、内存管理

概述:

  C预处理器处理程序的源代码,在编译器之前运行,通常以符号#开头。

  还会涉及到static、const的知识点...有的和java类同...有的容易混淆T.T。

  本章很多以前都没有接触过,在笔试中见过...如果有什么错误,欢迎指正~~

 

5.1 C预处理器

C语言的预处理主要有三个方面的内容:

1)宏定义与宏替换;

2)文件包含;

3)条件编译。

考点比较少,讲述一下对应需要注意的地方。

宏替换的本质很简单--文本替换。关于宏定义与宏替换请注意一下几点:

1)宏名一般用大写,宏名和参数的括号间不能有空格,宏定义末尾不加分号;

2)宏替换只作替换,不做语法检查、不做计算、不做表达式求解;

3)宏替换在编译前进行,不分配内存,函数调用在编译后程序运行时进行,并且分配内存;

4)函数只有一个返回值,利用宏则可以设法得到多个值;

5)宏替换使源程序变长,函数调用不会;

6)宏替换不占运行时间,只占编译时间,函数调用占运行时间(分配内存、保留现场、值传递、返回值)。

#inlcude接受两种形式的文件包含

1)<>形式:标准头文件,编译器会在路径的环境变量的位置中查找头文件。

2) ""形式:非系统文件,通常在源文件所在的路径中查找。

 

5.2 全局变量与局部变量

全局变量:也称为外部变量,定义在函数外部,不属于哪一个函数,属于一个源程序文件。

知识点1:在不用文件中引用另一个文件的全局变量,可以用引用头文件的方式,也可以使用extern关键字。有一点区别,如果这个变量写错了,引用头文件的方式在编译期间就会报错,用extern关键字在编译期间不会报错,在连接期间才会报错。

知识点2:在C等面向过程语言中,局部变量可以和全局变量重名,并且局部变量会屏蔽全局变量。

知识点3:在同一个文件中,当局部变量屏蔽了全局变量,又想使用的话,有两种方法,一种是使用域操作符"::",一种是使用"extern"。

int count = 3;

int main(void) {
    int i, sum, count = 2;
    for(i = 0, sum = 0; i < count ; i+=2, count++) {
        static int count = 4;
        count++;
        if(i%2 == 0) {
            extern int count;
            count++;
            sum += count;  //语句1
        } 
       sum += count; //语句2
    }
    printf("%d  %d", count, sum); 
    return 0;
}

上面的例子中,两个语句的输出分别是4和20。

需要分清各个count的范围,for循环判断的是main下第一行的count,for循环里面的是static的count,if语句里面的是外部的count,语句2的时候是for里面也就是static的count,printf中的count是main下第一行的count。需要细细研究,可以自己改改代码看看输出。

 

5.3 static 

不考虑类的话,static的作用主要有三条:

1)如果全局变量或者函数前面加上statc,就会对其他源文件隐藏,不必担心命名冲突。

2)未初始化的全局静态变量与局部静态变量会默认初始化为0。

3)static可以保持局部变量内容的持久。

类中static的作用表示属于一个类而不是属于此类的某一个对象(与java作用相同)

静态数据成员:对所有实例可见,必须在类定义体的外部定义。

静态成员函数:无法访问属于类对象的费静态数据成员,也无法访问非静态成员函数(因为普通成员函数总是某个具体对象,一般都隐含了一个this指针,指向对象本身,而静态成员没有)。

有两点需要说明:

1)静态成员之间可以相互访问;非静态成员函数可以任意地访问静态成员函数和静态数据成员;

2)由于没有this指针的开销,因此静态成员函数与类的非静态成员函数相比速度上会有少许的增长。

3)静态成员函数不能被声明为虚函数、volatile。

class base {
    virtual static void dunc1();  // 错误
    static void fun2() const; // 错误
    static void fun3() volatile; // 错误
}

 

5.4 const

C++中,const限定符把一个对象转换成一个常量。

指向const的指针:"cp是一个指针,它指向一个const double",因此它指向的东西是不能被改变的。

const double *cp;

const指针:"cp是一个指针,这个指针式指向double的const指针",本身不能改变,但是指向的值可以改变。

double d = 1.0;
double * const cp = &d;

const最具威力的用法是面对函数声明时的应用。在一个函数声明式内,const可以和函数返回值、各参数、函数本身(如果是成员函数)产生关联。

const修饰返回值:由于函数不能返回指向局部栈变量的指针,因为在函数返回后,栈被清理了,但是可以返回指向堆中分配的存储空间的指针或指向静态存储区的指针,在函数返回后它们仍然有效。

const修饰函数的参数:如果是传地址,我们应该尽可能的用const来修饰,如果不这样,就使得指向const的指针不能作实参。如:

int fun(int * i);   // 编译错误 "const *int类型的实参与int*类型的形参不兼容"
const int a = 1;  // 应改为 int fun(const int *i)
fun(&a);

const在类中的应用

const成员函数:

class base {
    void func1();
    void func2() const;
};

上述代码中,函数func2是类base的常量成员函数,fun2()函数末尾声明的const改变了隐含的this形参的类型,使this形参指向的对象为const类型(this本身类型为base* const,函数末尾声明了const后,this的类型为const base* const,即this指向的对象也为const)。const成员函数不能修改调用该函数的对象(mutable成员除外)。

const实施于成员函数的目的,是为了确保该成员函数可作用于const对象本身上。const对象、指向const对象的指针或引用只能调用其const成员函数,如果尝试用它们来调用非const成员函数,则是错误的。而非const对象可调用非const成员函数与const成员函数。显然,若不存在const成员函数,那么const对象的操作就极为困难,无法调用任何成员函数,实现const成员函数,使得可以对const对象产生操作。

const数据成员:必须在构造函数的成员初始化列表中初始化。

struct Thing {
    Thing() : valueB(1){... }  // 必须加上  
    int valueA;
    const int valueB;
};
Thing t;

有关static、const、static const成员变量的初始化问题:

1)static静态成员变量不能在类的内部初始化,在类的内部知识声明,定义必须在类定义体的外部。

2)const成员变量也不能在类定义处初始化,只能通过构造函数初始化列表进行,并且必须有构造函数。

3)const数据成员只在某个对象生存期间是常量,而对于整个类而言却是可变的,因为类可以创建多个对象,不同的对象其const成员的值可以不同。

4)要想建立整个类中都恒定的常量,应该用类中的枚举常量来实现或者用static const。如下例:

class Test {
    public:
        Test() : a(0) {}
        enum {size1 = 100, size2 = 200};
    private:
        const int a: // 只能在构造函数初始化列表中初始化
        static int b; // 在类的实现文件中定义并初始化
        const static int c:// c为整型可在此初始化 仍需在类定义体外进行定义
                                 //如果c为非整型,不能再此处初始化
};
int Test :: b = 0;
const int Test :: c = 0; //给const static成员变量赋值时,不需要加static,要有const   

 

5.5 内存管理与释放

一个C/C++的程序,用户使用的内存主要分为以下几个部分:栈、堆、静态存储区、文字常量区、代码区。

5.5.1 C内存操作

C语言中用malloc和free分配和释放存储空间。

void GetMemory(char *p) {
    p = (char *)malloc(11);
}

int main(void) {
    char *str = "Hello";
    GetMemory(str);
    strcpy(str, "Hello World");
    printf("%s", str);
    return 0;
}

上诉代码是有问题的,开始时str是指向文字常量区的指针,GetMemory(str)并不会为str新分配空间。

在函数调用传参时,str和形参p虽然指向相同,但它们自身的地址是不同的,是两个不同的变量:

p在执行malloc之后就指向不同的位置了,随后因为p是局部变量而被释放(但malloc的空间没有free,成为无法被应用的空间)。

可见str一直都是指向"Hello"的,即str指向文字常量区,而文字常量区是不允许修改的,故调用strcpy时会出错。

5.5.2 C++内存管理

C++使用new和delete实现分配和释放存储空间。

1)动态创建对象的初始化

通常,动态创建对象如果不提供显示初始化,那么对于类类型的对象,用该类的默认构造函数初始化;而内置类型的对象则无初始化。如:

string *ps = new string; // 调用默认构造函数初始化
int *pi = new int ;// 无初始化

同样,也可以显示对动态创建的对象做初始化。

string *ps = new string();  // 调用默认构造函数初始化
int *pi = new int();  // pi指向一个初始化为0的int

可见,对于提供了默认构造函数的类类型(如string),没有必要对其对象进行显示初始化:无论程序是明确地不初始化还是要求进行初始化,都会自动调用其默认构造函数初始化该对象。而对于内置类型或没有定义默认构造函数的类型,采用不同初始化方法则有显著的差别:

int *pi = new int;  // 没有初始化
int *pi = new int(); // 初始化为0

动态创建的对象用完后,程序员必须显示地将该对象占用的内存释放给自由存储区。

在回收用new分配的单个对象的内存空间时用delete,回收用new[]分配的一组对象的内存空间时用delete[]。

2)const对象的动态分配和回收

动态创建const对象:

const int*pci = new const int(1024);  // 必须在创建时初始化 返回指向const int对象的指针
const string *pcs = new const string;  // 隐式初始化,调用其构造函数

删除const对象:

尽管程序不能改变const对象的值,但是可以删除对象本身,如同其他动态对象一样,const动态对象也是使用删除指针来释放的:

delete pci ; // 即使是指向const int对象的指针,也可有效的回收pci所指向的内容

最后是一道百度2012的笔试题:

malloc/free和new/delete的区别

1)malloc与free是C/C++语言的标准库函数,new/delete是C++的运算符;

2)new自动计算需要分配的空间,而malloc需要手动计算字节数;

3)new是类型安全的,而malloc不是,比如:

int* p = new float[2]; // 编译时指出错误
int* p = (int*)malloc(2*sizeof(double)); // 编译时无法指出错误

4)new调用operator new分配足够的空间,并调用相关对象的构造函数,而malloc不能调用构造函数;delete将调用该实例的析构函数,然后调用类的operator delete,以释放该内存实例占用的空间,而free不能调用析构函数;

5)malloc/free需要库文件指出,new/delete则不需要。

 

 

大体上这一部分就写这么多了,实际的笔试题中还会考察一段代码,找到其中内存泄露的大小,需要分清楚情况和作用范围就好,这块理解的也不是很深,如果有不明白或者错误的地方,欢迎一起探讨~~  希望大家都有所收获~~  0.0

 

返回目录 ->  C/C++基础--笔试突击 概述

 

posted @ 2015-08-15 13:20  可爱的波儿胖  阅读(872)  评论(0编辑  收藏  举报

友情链接 : CodeForge源码分享