[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++基础--笔试突击 概述

时间: 2024-10-17 23:33:57

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

[C/C++基础--笔试突击] 7.指针与引用

概述: 比较抽象的但又很有用的东西 0.0 void*指针:可以保存任何类型对象的地址. 指向指针的指针 函数指针 7.1 指针 一个有效的指针必然是一下三种状态之一: 1)保存一个特定对象的地址: 2)指向某个对象后面的另一个对象 3)0值. 若指针保存0值,表明它不指向任何对象.未初始化的指针是无效的,直到给该指针赋值后,才可使用. 注:*p++和(*p)++不等价,单目运算符*的优先级比++高,故*p++先完成取值操作,然后对指针地址执行++操作,而(*p)++是首先执行屈指操作,然后对该

[C/C++基础--笔试突击] 6.函数

概述: 函数是有名字的计算单元,对程序的结构话至关重要. C++中,函数原型就是函数的声明,要加上分号. 这一部分还是比较轻松的~~ 6.1 参数传递 形参:出现在函数定义中,在整个函数体内都可以使用,函数体结束后被收回. 实参:主函数中调用,进入被调函数后,实参不能使用. 形参和实参的功能是数据传送,发生函数调用时,主调函数把实参的值传给被调函数的形参,从而实现主调函数向被调函数的数据传送. 函数调用时,C里面的两种传递: 1)值传递 2)指针传递(严格来说也属于值传递,只不过传递的是地址)

[C/C++基础--笔试突击] 4.运算符及优先级

概述: 表达式,由操作数和运算符组成. 笔试中通常的考点有操作符的优先级.异或等关系运算. 4.1 赋值语句 赋值运算符"=",操作符左边代表着存储单元的地址,称为左值,右边带表着需要的值,称为右值. 注:赋值操作符的左操作数必须是非const的左值. int const& max(int const& a, int const& b) { return a > b ? a : b; } int& fun(int& a) { a += 5;

C 基础 - 预处理器与C库

C预处理器在程序执行之前查看程序. 预处理器不做计算,不对表达式求值,只进行替换. 预处理器指令: #define (符号常量), 定义时组成部分如下: * #deinfine 符号指令 * 宏 * 替换列表 对于大部分的数字常量,可以使用符号常量. #define 中还可以使用参数 #define SQUARE(X) X*X z = SQUARE(2); 文件包含: #include 指令 当预处理器发现#include指令时,会查看后面的文件名把文件的内容包含到当前文件中.

C基础知识(10):预处理器

C预处理器不是编译器的组成部分,但是它是编译过程中一个单独的步骤.简言之,C预处理器只不过是一个文本替换工具而已,它们会指示编译器在实际编译之前完成所需的预处理. 所有的预处理器命令都是以井号(#)开头.它必须是第一个非空字符,为了增强可读性,预处理器指令应从第一列开始. (1) 预处理器实例 1 // 包含一个源代码文件(从系统库中获取 stdio.h,并添加文本到当前的源文件中) 2 #include <stdio.h> 3 4 // 定义宏(把代码中所有的FILE_SIZE替换为20)

.NET基础拾遗(1)类型语法基础和内存管理基础2

二.内存管理和垃圾回收 2.1 .NET中栈和堆的差异? 每一个.NET应用程序最终都会运行在一个OS进程中,假设这个OS的传统的32位系统,那么每个.NET应用程序都可以拥有一个4GB的虚拟内存..NET会在这个4GB的虚拟内存块中开辟三块内存作为 堆栈.托管堆 以及 非托管堆. (1).NET中的堆栈 堆栈用来存储值类型的对象和引用类型对象的引用(地址),其分配的是一块连续的地址,在.NET应用程序中,堆栈上的地址从高位向低位分配内存,.NET只需要保存一个指针指向下一个未分配内存的内存地址

PHP (超文本预处理器)

1.PHP(外文名:PHP: Hypertext Preprocessor,中文名:"超文本预处理器")是一种通用开源脚本语言.语法吸收了C语言.Java和Perl的特点,利于学习,使用广泛,主要适用于Web开发领域.PHP 独特的语法混合了C.Java.Perl以及PHP自创的语法.它可以比CGI或者Perl更快速地执行动态网页.用PHP做出的动态页面与其他的编程语言相比,PHP是将程序嵌入到HTML(标准通用标记语言下的一个应用)文档中去执行,执行效率比完全生成HTML标记的CGI

(转载)虚幻引擎3--9掌握虚幻技术UnrealScript 预处理器

第九章 – UNREALSCRIPT预处理器 9.1概述 9.2 MACRO(宏)的基础知识 指南 9.1 –您的第一个宏 9.3具有参数的宏 指南 9.2 –       MACRO参数 9.4内置宏 DEFINE IF/ELSE/ENDIF 实例:         IF/ELSE/ENDIF的应用 INCLUDE ISDEFINED/NOTDEFINED 示例: 结合使用         IF/ELSE/ENDIF 和 ISDEFINED/NOTDEFINED UNDEFINE LOG/WA

.NET基础拾遗(1)类型语法基础和内存管理基础

一.基础类型和语法 1.1 .NET中所有类型的基类是什么? 在.NET中所有的内建类型都继承自System.Object类型.在C#中,不需要显示地定义类型继承自System.Object,编译器将自动地自动地为类型添加上这个继承申明,以下两行代码的作用完全一致: public class A { } public class A : System.Object { } 1.2 值类型和引用类型的区别? 在.NET中的类型分为值类型和引用类型,它们各有特点,其共同点是都继承自System.Ob