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