logo资料库

C++基础知识.docx

第1页 / 共32页
第2页 / 共32页
第3页 / 共32页
第4页 / 共32页
第5页 / 共32页
第6页 / 共32页
第7页 / 共32页
第8页 / 共32页
资料共32页,剩余部分请下载后查看
一、关于字符串和指针的一些问题
1.1
1.2
1.3
1.4
1.5
1.6
1.7
1.8
1.9
二、问答题
1.1、写出完整版的strcpy函数
1.2、分别给出bool,int,float,指针变量与“零值”比较的 if 语句(变量名为var)
1.3、以下为Windows NT下的32位C++程序,请计算sizeof的值
1.4、写一个“标准”宏MIN,这个宏输入两个参数并返回较小的一个。另外,当你写下least = M
1.5、为什么标准头文件都有类似以下的结构?
2.1、请说出static和const关键字尽可能多的作用
2.2、static关键字的作用
2.3、extern关键字的作用
2.4、volatile关键字的作用
2.5、registr和typdef关键字的作用
2.6、explicit关键字的作用
2.7、new和malloc的区别,delete和free的区别
2.8、typedef和define的区别
2.9、const与#define的区别
3.1、指针和引用是什么?有什么区别?
3.2、指针和数组的区别
3.3、指针数组和数组指针
3.4、指针常量和常量指针
3.5、野指针是什么?成因是什么?
3.6、什么是函数指针?
3.7、形参传入一个指针和一个引用的区别?
3.8、智能指针
3.9、auto_ptr类与shared_ptr类的比较
4.1、虚函数的作用?
4.2、虚函数和静态函数的区别
4.3、虚函数和多态
4.4、虚函数表、纯虚函数、抽象类
4.5、虚函数表编译器是如何处理的?
4.6、析构函数的作用
4.7、父类的析构函数必须是虚函数,C++默认的析构函数不是虚函数
4.8、构造函数一般不定义为虚函数,而析构函数一般写成虚函数
4.9、什么情况下需要将析构函数定义为虚函数?
4.10、钻石问题和虚继承
5.1、内存管理
5.2、内存对齐的原则以及作用?
5.3、内存溢出,内存泄漏
5.4、栈与队列的区别
5.5、栈与堆的区别
5.6、为什么栈的速度比堆要快?
5.6、malloc的原理?
5.7、动态绑定与静态绑定
5.8、深拷贝、浅拷贝
5.9、拷贝构造函数调用的三种情况
5.10、实现类对象只能静态分配或动态分配
5.11、计算下面几个类的大小
5.12、C++中的未定义行为
6.1、源文件从文本到可执行文件经历的过程?
6.2、include头文件的顺序以及双引号””和尖括号<>的区别?
6.3、宏定义和内联函数的区别
6.4、Struct结构体、Union联合体、Class类
6.5、成员列表初始化的使用情况
6.6、为什么要使用成员列表初始化
6.7、Makefile文件的作用?
6.8、调用惯例(约束)针对哪些问题?
6.9、C++有哪些调用惯例?
7.1、重载、重写、隐藏
7.2、什么是多态性
7.3、封装、继承和多态
7.4、四种cast转换
7.5、i++是否为原子操作?
7.6、++i和i++的实现
7.7、C++和C的区别
7.8、C++和Java的比较
8.1、STL
8.2、STL的优点
8.3、STL三大组件
8.4、STL中迭代器的作用,有指针为何还要迭代器?
8.5、STL迭代器是怎么删除元素的?
8.6、map和set
8.7、vector和list
8.8、string和char*
8.9、deque和vector
8.10、STL容器使用时机
8.11、STL容器使用场景
9.1、说说fork,wait,exec函数
9.2、描述一下epoll的原理?
10.1、面向对象和面向过程的区别?优缺点是什么?
10.2、设计模式的六大原则
Create By No.5 C++基础知识 一、关于字符串和指针的一些问题 1.1 void test1(){ char string[10]; char* str1="0123456789"; strcpy(string,str1); } 字符串 str1 需要 11 个字节才能存放下(包括末尾的’\0’),而 string 只有 10 个字节的空间 strcpy 会导致数组越界; 1.2 void test2(){ char string[10], str1[10]; int i; for(i=0; i<10;i++){ str1 ='a';} strcpy(string,str1); } 首先,代码根本不能通过编译。数组名 str1 为 char *const 类型的右值类型,根本不能赋值。 再者,即使想对数组的第一个元素赋值,也要使用 *str1 = 'a'; 其次,对字符数组赋值后,使用库函数 strcpy 进行拷贝操作,strcpy 会从源地址一直往后拷贝,直到遇到'\0'为止。所以 拷贝的长度是不定的。如果一直没有遇到'\0'导致越界访问非法内存,程序就崩了。 修改方案: void test2(){ char string[10], str1[10]; int i; for(i=0; i<9; i++) {str1[i] ='a';} str1[9] ='\0'; strcpy( string, str1 ); } 1.3 void test3(char* str1){ if(str1 == NULL){ return ;} char string[10]; if(strlen(str1)<=10){ strcpy(string,str1);} } if(strlen(str1) <= 10)应改为 if(strlen(str1) < 10),因为 strlen 的结果未统计’\0’所占用的 1 个字节。 1.4 void GetMemory( char*p){ p=(char*)malloc(100); } void Test(void){ char* str=NULL; GetMemory(str); strcpy(str,"hello world"); printf(str); 1
} Create By No.5 传入中 GetMemory(char*p)函数的形参为字符串指针,在函数内部修改形参并不能真正的改变传入形参的实参值, 执行完 char*str=NULL 和 GetMemory(str)后的 str 仍然为 NULL; 1:传入形参并不能真正改变形参的值,执行完之后为空; 2:在函数 GetMemory 中和 Test 中没有 malloc 对应的 free,造成内存泄露 1.5 char* GetMemory(void){ char p[]="hello world"; return p; } void Test(void){ char* str=NULL; str=GetMemory(); printf(str); } char p[]="hello world";相当于 char p[12],strcpy(p," hello world" ).p 是一个数组名,属于局部变量,存储在栈中,"hello world"存储在文字存储区,数组 p 中存储的是"hello world"的一个副本,当函数结束,p 被回收,副本也消失了(确切的说 p 指向的栈存储区被取消标记,可能随时被系统修改),而函数返回的 p 指向的内容也变得不确定,文字存储区的"hello world"未改变。 可以这样修改: a)、char* p= " hello world" ; return p; 这里 p 直接指向文字存储区的"hello world",函数按值返回 p 存储的地址,所以有 效。 b)、static char p[]= "hello world" ; return p; static 指出数组 p 为静态数组,函数结束也不会释放,所以有效。 1.6 void GetMemory(char**p, int num){ *p=(char*)malloc(num); } void Test(void){ char* str=NULL; GetMemory(&str,100); strcpy(str,"hello"); printf(str); } 1、申请失败判断: if(*p==NULL) { perror("malloc fail"); return -1; } 2、未释放内存 free(str)及释放后 str=NULL,否则可能会导致野指针出现。 1.7 void Test(void){ char* str=(char*)malloc(100); strcpy(str,"hello"); free(str); //省略的其它语句 } 2
Create By No.5 在执行 char*str=(char*)malloc(100);后未进行内存是否申请成功的判断; 另外,在 free(str)后未置 str 为空,导致可能变成一个“野”指针,应加上:str=NULL; 1.8 swap(int* p1,int* p2){ int* p; *p=*p1; *p1=*p2; *p2=*p; } 参考答案: 1.需要一个返回值 void 2 在 swap 函数中,p 是一个野指针,有可能指向系统区,导致程序运行的崩溃。 程序应改为: void swap( int* p1,int* p2 ){ int p; p = *p1; *p1 = *p2; *p2 = p; } 1.9 以下四行代码的区别是什么? const char * arr = "123"; //字符串 123 保存在常量区,const 本来是修饰 arr 指向的值不能通过 arr 去修改,但是字符串“123”在常量区,本 来就不能改变,所以加不加 const 效果都一样 char * brr = "123"; //字符串 123 保存在常量区,这个 arr 指针指向的是同一个位置,同样不能通过 brr 去修改"123"的值 const char crr[] = "123"; //这里 123 本来是在栈上的,但是编译器可能会做某些优化,将其放到常量区 char drr[] = "123"; //字符串 123 保存在栈区,可以通过 drr 去修改 二、问答题 1.1、写出完整版的 strcpy 函数 1、不考虑内存重叠 char* strcpy( char *strDest, const char *strSrc){ assert((strDest != NULL) && (strSrc != NULL)); char *address = strDest; while((*strDest++=*strSrc++)!=‘\0’); return address; } 2、考虑内存重叠 char s[10]="hello"; strcpy(s, s+1); //应返回 ello, //strcpy(s+1, s); //应返回 hhello,但实际会报错,因为 dst 与 src 重叠了,把'\0'覆盖了 3
所谓重叠,就是 src 未处理的部分已经被 dst 给覆盖了,只有一种情况:src<=dst<=src+strlen(src) C 函数 memcpy 自带内存重叠检测功能,下面给出 memcpy 的实现 my_memcpy。 Create By No.5 char * strcpy(char *dst,const char *src){ assert(dst != NULL && src != NULL); char *ret = dst; my_memcpy(dst, src, strlen(src)+1); return ret; } char *my_memcpy(char *dst, const char* src, int cnt){ assert(dst != NULL && src != NULL); char *ret = dst; if (dst >= src && dst <= src+cnt-1) { //内存重叠,从高地址开始复制 dst = dst+cnt-1; src = src+cnt-1; while (cnt--) *dst-- = *src--; } else{ //正常情况,从低地址开始复制 while (cnt--) *dst++ = *src++; } return ret; } 1.2、分别给出 bool,int,float,指针变量与“零值”比较的 if 语句(变量名为 var) bool 型变量: if(!var) int 型变量: if(var==0) float 型变量: const float EPSINON = 0.00001; if ((x >= - EPSINON) && (x <= EPSINON) 指针变量:if(var==NULL) 1.3、以下为 Windows NT 下的 32 位 C++程序,请计算 sizeof 的值 void Func ( char str[100] ){ sizeof( str )=? } void *p = malloc(100); sizeof(p)=? 参考答案: sizeof(str)=4、sizeof(p)=4 Func(char str[100])函数中数组名作为函数形参时,在函数体内,数组名失去了本身的内涵,仅仅只是一个指针;在 失去其内涵的同时,它还失去了其常量特性,可以作自增、自减等操作,可以被修改 数组名的本质如下: 1)数组名指代一种数据结构,这种数据结构就是数组;例如: char str[10]; 4
Create By No.5 cout <
为了实现 C 和 C++的混合编程,C++提供了 C 连接交换指定符号 extern "C"来解决名字匹配问题,函数声明前加上 extern "C"后,则编译器就会按照 C 语言的方式将该函数编译为_foo,这样 C 语言中就可以调用 C++的函数了。 Create By No.5 2.1、请说出 static 和 const 关键字尽可能多的作用 static 关键字: 1)函数体内 static 变量的作用范围为该函数体,不同于 auto 变量,该变量的内存只被分配一次,因此其值在下次调用 时仍维持上次的值; 2)在模块内的 static 全局变量可以被模块内所用函数访问,但不能被模块外其它函数访问; 3)在模块内的 static 函数只可被这一模块内的其它函数调用,这个函数的使用范围被限制在声明它的模块内; 4)在类中的 static 成员变量属于整个类所拥有,对类的所有对象只有一份拷贝; 5)在类中的 static 成员函数属于整个类所拥有,这个函数不接收 this 指针,因而只能访问类的 static 成员变量。 const 关键字: 1)欲阻止一个变量被改变,可以使用 const 关键字。在定义该 const 变量时,通常需要对它进行初始化,因为以后就没 有机会再去改变它了; 2)对指针来说,可以指定指针本身为 const,也可以指定指针所指的数据为 const,或二者同时指定为 const; 3)在一个函数声明中,const 可以修饰形参,表明它是一个输入参数,在函数内部不能改变其值; 4)对于类的成员函数,若指定其为 const 类型,则表明其是一个常函数,不能修改类的成员变量; 5)对于类的成员函数,有时候必须指定其返回值为 const 类型,以使得其返回值不为“左值”。 例如: const classA operator*(const classA& a1,const classA& a2); operator*的返回结果必须是一个 const 对象。如果不是,这样的变态代码也不会编译出错: classA a, b, c; (a * b) = c; //对 a*b 的结果赋值 操作(a * b) = c 显然不符合编程者的初衷,也没有任何意义。 2.2、static 关键字的作用 1.全局静态变量 在全局变量前加上关键字 static,全局变量就定义成一个全局静态变量. 内存中的位置:静态存储区,在整个程序运行期间一直存在。 初始化:未经初始化的全局静态变量会被自动初始化为 0(自动对象的值是任意的,除非他被显式初始化); 作用域:全局静态变量在声明他的文件之外是不可见的,准确地说是从定义之处开始,到文件结尾。 2.局部静态变量 在局部变量之前加上关键字 static,局部变量就成为一个局部静态变量。 内存中的位置:静态存储区 初始化:未经初始化的全局静态变量会被自动初始化为 0(自动对象的值是任意的,除非他被显式初始化); 作用域:作用域仍为局部作用域,当定义它的函数或者语句块结束的时候,作用域结束。但是当局部静态变量离开作 用域后,并没有销毁,而是仍然驻留在内存当中,只不过我们不能再对它进行访问,直到该函数再次被调用, 并且值不变; 3.静态函数 在函数返回类型前加 static,函数就定义为静态函数。函数的定义和声明在默认情况下都是 extern 的,但静态函数只 是在声明他的文件当中可见,不能被其他文件所用。 函数的实现使用 static 修饰,那么这个函数只可在本 cpp 内使用,不会同其他 cpp 中的同名函数引起冲突; 6
Create By No.5 warning:不要再头文件中声明 static 的全局函数,不要在 cpp 内声明非 static 的全局函数,如果你要在多个 cpp 中复 用该函数,就把它的声明提到头文件里去,否则 cpp 内部声明需加上 static 修饰; 4.类的静态成员 在类中,静态成员可以实现多个对象之间的数据共享,并且使用静态数据成员还不会破坏隐藏的原则,即保证了安全 性。因此,静态成员是类的所有对象中共享的成员,而不是某个对象的成员。对多个对象来说,静态数据成员只存储 一处,供所有对象共用 5.类的静态函数 静态成员函数和静态数据成员一样,它们都属于类的静态成员,它们都不是对象成员。因此,对静态成员的引用不需 要用对象名。 在静态成员函数的实现中不能直接引用类中说明的非静态成员,可以引用类中说明的静态成员(这点非常重要)。如 果静态成员函数中要引用非静态成员时,可通过对象来引用。从中可看出,调用静态成员函数使用如下格式:<类名>::< 静态成员函数名>(<参数表>); 1、对于函数定义和代码块之外的变量声明,static 修改标识符的链接属性,由默认的 external 变为 internal,作用域和存 储类型不改变,这些符号只能在声明它们的源文件中访问。 2、对于代码块内部的变量声明,static 修改标识符的存储类型,由自动变量改为静态变量,作用域和链接属性不变。这 种变量在程序执行之前就创建,在程序执行的整个周期都存在。 3、对于被 static 修饰的普通函数,其只能在定义它的源文件中使用,不能在其他源文件中被引用 4、对于被 static 修饰的类成员变量和成员函数,它们是属于类的,而不是某个对象,所有对象共享一个静态成员。静态 成员通过<类名>::<静态成员>来使用。 2.3、extern 关键字的作用 extern 置于变量或函数前,用于标示变量或函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块 中寻找其定义。它只要有两个作用: 当它与“C”一起连用的时候,如:extern "C" void fun(int a,int b);则告诉编译器在编译 fun 函数时候按着 C 的规矩 去翻译,而不是 C++的(这与 C++的重载有关,C++语言支持函数重载,C 语言不支持函数重载,函数被 C++编译器编 译后在库中的名字与 C 语言的不同) 当 extern 不与“C”在一起修饰变量或函数时,如:extern int g_Int;它的作用就是声明函数或全局变量的作用范围 的关键字,其声明的函数和变量可以在本模块或其他模块中使用。 2.4、volatile 关键字的作用 用来修饰变量的,表明某个变量的值可能会随时被外部改变,因此这些变量的存取不能被缓存到寄存器,每次使用 需要重新读取。 假如有一个对象 A 里面有一个 boolean 变量 a,值为 true,现在有两个线程 T1,T2 访问变量 a, T1 把 a 改成了 false 后 T2 读取 a; T2 这时读到的值可能不是 false,即 T1 修改 a 的这一操作,对 T2 是不可见的。 发生的原因可能是,针对 T2 线程,为了提升性能,虚拟机把 a 变量置入了寄存器(即 C 语言中的寄存器变量), 这样就会导致,无论 T2 读取多少次 a,a 的值始终为 true,因为 T2 读取了寄存器而非内存中的值。声明了 volatile 或 synchronized 后,就可以保证可见性,确保 T2 始终从内存中读取变量,T1 始终在内存中修改变量。总结:防止脏读, 增加内存屏障。 7
2.5、registr 和 typdef 关键字的作用 register 关键字的作用: Create By No.5 请求 CPU 尽可能让变量值保存在 CPU 内部的寄存器中,减去 CPU 从内存中抓取数据的时间,提高程序运行效率。 使用 register 关键字应注意什么? 1、只有局部变量才可以被声明用 register 修饰 (register 不能修饰全局变量和函数的原因:全局变量可能被多个进程访问,而用 register 修饰的变量,只能被当前 进程访问) 2、不能用取地址获取用 register 修饰的变量的地址(原因:变量保存在寄存器中,而取地址获取的地址的是内存的地址) 3、用 register 修饰的变量一定要是 CPU 所接受的数据类型 typedef 关键字的作用: 给数据类型定义一个新名字, 1、提高了移植 2、简化复杂的类型声明,提高编码效率 3、解释数据类型的作用 2.6、explicit 关键字的作用 C++中,一个参数的构造函数(或者除了第一个参数外其余参数都有默认值的多参构造函数), 承担了两个角色。 1 是个构造器 2 是个默认且隐含的类型转换操作符。 所以,有时候在我们写下如 AAA = XXX,这样的代码,且恰好 XXX 的类型正好是 AAA 单参数构造器的参数类型,这时 候编译器就自动调用这个构造器,创建一个 AAA 的对象。 这样看起来好象很酷,很方便。但在某些情况下(见下面权威的例子),却违背了我们(程序员)的本意。这时候 就要在这个构造器前面加上 explicit 修饰,指定这个构造器只能被明确的使用,不能作为类型转换操作符被隐含的使用。 class Test1{ public: } //普通构造函数 Test1(int n) { num=n; int num; private: }; class Test2{ public: explicit Test2(int n) { } //explicit(显式)构造函数 //隐式调用其构造函数,成功 //编译错误,不能隐式调用其构造函数 //显式调用成功 num=n; int num; private: }; int main() { Test1 t1=12; Test2 t2=12; Test2 t2(12); return 0; } Test1 的构造函数带一个 int 型的参数,Test1 t1=12 会隐式转换成调用 Test1 的这个构造函数。而 Test2 的构造函 数被声明为 explicit(显式),这表示不能通过隐式转换来调用这个构造函数,因此 Test2 t2=12 会出现编译错误。 普通构造函数能够被隐式调用。而 explicit 构造函数只能被显式调用。 8
分享到:
收藏