C++学习(16)

1、 int a=5,则++(a++)的值是“ 编译错误”

分析a++返回一个值也就是5,++操作符只能作用于变量,而不能是一个数字。

此处的a++是表达式,++不能对表达式作自增。

 

2、 const int *x;//const修饰的是int *x,指针变量x指向整型常数,x值可以改变,但不能试图改变指向的整型常数。(所指对象不能改变,指针可以改变)

 

int * const x;//const修饰的是x,指针变量x的值不能改变。(指针不能改变,所指对象可以改变)

 

分析:左数右指。当const出现在*号左边时,指针指向的数据为常量。当const出现在*号右边,指针本身为常量。

 

3、 共享内存:是在2个正在运行的进程间传递数据的一种非常有效方式。共享内存允许两个不相关进程访问同一个逻辑内存。由于它们并没有提供同步机制,所以我们通常需要用其他的机制来同步访问共享的内存。

 

进程间通信方法有:文件映射、共享内存、匿名管道、命名管道、邮件槽、剪切板、动态数据交换、对象连接与嵌入、动态连接库、远程过程调用等

 

几种进程间的通信形式:管道、有名管道、信号量、信号、共享内存、消息队列、套接字

 

几种线程间的同步形式:事件、临界区、互斥量、信号量

 

4、 C++中定义了箭头运算符,箭头运算符把解引用和成员访问两个操作结合在一起,也就是说it->men(*it).men表达的含义相同,那么就是说 pData->data[0]实际上指的是数组首元素而不是地址。

 

5、代码是否编译有错?如何改?

template <class T>
struct sum {
         staticvoid foo(T op1,T op2) {
                   cout<<op1<<op2;
         }
}
sum::foo(1,3);

调用一个如下:sum::<int>foo(1,3)

 

分析:这道题的考点是类模板的static成员。首先说说模板实例化,即类模板需要指定确定的类型,例如本题中sum<int> a或者sum<string> a等。   static成员函数可以由类名或者对象直接调用。在本题中,sum<int>  aa.foo(1,3)或者sum<int>::foo(1,3)。两种形式均可。

 

static成员函数,static成员是类的组成部分并不是任何对象的组成部分,因此,static成员函数没有this指针。我们知道,一般而言,类中的成员函数具有一个附加的隐含实参,即指向该类对象的一个指针。这个隐含实参命名为this。因为static成员函数不是任何对象的组成部分,所以static成员函数就没有this形参了。由于成员函数声明为const说明该成员函数不会修改该成员函数所属的对象,所以static成员函数不能声明为const。为什么呢?因为static成员函数不是任何对象的组成部分。static成员函数可以直接访问所属类的static成员,但是不能直接使用非static成员函数!也不能访问static const 类型的成员!

 

补充:

1) 静态成员变量可被该类的所有方法访问;(静态成员属于整个类,而不是某个对象,可以被所有方法访问,所有子类也可以访问父类静态成员)

2) 该类的静态方法只能访问该类的静态成员函数;(不能访问非静态成员函数)

3) 静态成员可以被任一对象修改,修改后的值可以被所有对象共享;

4) 子类可以访问父类的静态成员

5) 静态成员无多态性

 

 

6、分析如下代码

void func1(char *e) {
         char*p1;
         p1=malloc(100);
         sprintf(p1,error:"%s.",e);
         local_log(p1);
}
//func1申请了空间没有释放
int func2(char *filename) {
         FILE*fp;
         intkey;
         fopen(filename,"r");
         fscanf(fp,"%d",&key);
         returnkey;
}
//func2打开了流没有关闭

void func3(char *info) {
         char*p,*pp;
         p=malloc(100);
         pp=p;free(p);
         sprintf(pp,*info:"%s.",info);free(pp);
}
//func3中free了两次,但是其实指针都是一块内存空间

sprintf函数的用法:

在将各种类型的数据构造成字符串时,sprintf的强大功能很少会让你失望的,

由于sprintfprintf在用法上几乎一样,知识打印的目的地不同而已,sprintf打印到字符串中,而printf则直接在命令行上输出。这也是sprintfprintf有用得多;

 

sprintf是个变参函数,定义如下:

int sprintf(char *buffer,const char*format[,argument ]..);

除了前俩个参数类型固定外,后面可以接任意多个参数。而它的精华,显然就在第二个参数;格式化字符串上。

 

printf sprintf都使用格式化字符串来指定串的格式,在格式串内部使用一些以“%”开头的格式,说明符(format specifications)来占据一个位置,在后边的便餐列表中提供相应的变量,最终函数就会用相应的变量来替代那个说明符,产生一个调用者想要的字符串。

 

格式化数字字符串:

sprintf 最常用的应用之一莫过于把整数打印到字符串中,所以sprintf在大多数场合可以替代itoa.

 

 

7、下面程序段,没有错误的是(A

Achar *sp,s[10];sp=”hello”;

Bchar *sp,s[10];s=”hello”;

Cchar str1[10]=”computer”,str2[10]; str2=str1;

Dchar mark[];mark=”program”;

分析:BCD不能对数组名赋值,因为数组名是一个指针。

B中不能直接将字符串赋值给数组名;C中不能将两个字符数组直接互相赋值;D中不能这样定义char mark[],必须指定长度或者初始化。

 

8、内联函数,当函数代码较小且被频繁调用的时候,最有用。

分析:内联函数只适合于只有15行的小函数。对一个含有许多语句的大函数函数调用和返回的开销相对来说微不足道,所以也没有必要用内联函数实现。

在内联函数内不允许用循环语句和开关语句。否则编译将该函数视同普通函数。

 

内联函数不是在调用时发生控制转移,而是在编译时将函数体嵌入在每一个调用处。编译时,类似宏替换,使用函数体替换调用处的函数名。一般在代码中用inline修饰,但是能否形成内联函数,需要看编译器对该函数定义的具体处理。内联扩展是用来消除函数调用时的时间开销。它通常用于频繁执行的函数。一个小内存空间的函数非常受益。

 

9、函数之间传递数据使用的是参数是在栈中申请的,mallocnew都是在堆中申请。

栈是系统维护,堆是自己申请释放。

 

10、声明纯虚函数的类是抽象类,不能实例化;基类被虚继承叫虚基类;子类如果不实现基类的所有纯虚函数,则子类也不能实例化;

 

百科:纯虚函数可以让类先具有一个操作名称,而没有操作内容,让派生类在继承时再去具体地给出定义。凡是含有纯虚函数的类叫做 抽象类 。这种类不能声明对象,只是作为基类为派生类服务。除非在派生类中完全实现基类中所有的的纯虚函数,否则,派生类也变成了抽象类,不能实例化对象。

 

11、分析如下代码:

#pragma pack(2)
class BU
{
int number; // 4
union UBffer
{
char buffer[13]; // 13
int number; // 4
}ubuf; // union的大小取决于它所有的成员中,占用空间最大的一个成员的大小,并且需要内存对齐,这里因为#pragma pack(2),所以union的大小为14,如果不写#pragma pack(2),那么union大小为16,因为与sizeof(int)=4对齐
void foo(){} //0 ,只是一个声明,声明不占用空间
typedef char*(*f)(void*); //0 ,函数方面只有虚函数占用空间,即便有多个也相当于一个指针大小。
enum{hdd,ssd,blueray}disk; // 4
}bu;

因此sizeofunion = 4+14 +0 +0 +4 = 22

 

12C代码中的,最后一行chardata[0];的作用:方便管理内存缓冲区、减少内存碎片化

typedef struct list_t {
         structlist_t *next;
         structlist_t *prev;
         chardata[0];
}list_t;

分析:柔性数组,它只能放在结构体末尾,

申明一个长度为0的数组,就可以使得这个结构体是可变长的。对于编译器来说,此时长度为0的数组并不占用空间,因为数组名本身不占空间,它只是一个偏移量,数组名这个符号本身代表了一个不可修改的地址常量(注意:数组名永远都不会是指针!),但对于这个数组的大小,我们可以进行动态分配 请仔细理解后半部分,对于编译器而言,数组名仅仅是一个符号,它不会占用任何空间,它在结构体中,只是代表了一个偏移量,代表一个不可修改的地址常量!

 

对于0长数组的这个特点,很容易构造出变成结构体,如缓冲区,数据包等等:

注意:构造缓冲区就是方便管理内存缓冲区,减少内存碎片化,它的作用不是标志结构体结束,而是扩展

柔性数组是C99的扩展,简而言之就是一个在struct结构里的标识占位符(不占结构struct的空间)。

 

 

13不能对引用的地址进行申请内存,否则会清空引用内所有指针函数。

 

补充:

void *memset(void *s, int ch, size_tn);

函数解释:将s中前n个字节typedef unsigned int size_t )用ch替换并返回s.

作用是在一段内存块中填充某个给定的值,它是对较大的结构体或数组进行清零操作的一种最快方法,通常为新申请的内存做初始化工作。

 


14、假设CSomething是一个类,执行下面这些语句后,内存里创建了6CSomething对象。

 

CSomething a()//没有创建对象,这里不是使用默认构造函数,而是定义了一个函数,在C++ Primer393页中有说明。

CSomething b(2);//使用一个参数的构造函数,创建了一个对象。

CSomething c[3];//使用无参构造函数,创建了3个对象。

CSomething &ra=b;//ra引用b,没有创建新对象。

CSomething d=b;//使用拷贝构造函数,创建了一个新的对象d

CSomething *pA = c;//创建指针,指向对象c,没有构造新对象。

CSomething *p = new CSomething(4);//新建一个对象。

//综上,一共创建了6个对象。

 

补充:注意区别:

CSomething a();

CSomething a;

前者是一个函数的声明,与默认构造函数区分;//返回类型为CSomething,形参表为空

后者是一个对象的定义;

 

15、浅层复制:只复制指向对象的指针,而不复制引用对象本身。(浅复制,若类中存在成员变量指针,修改一个对象一定会影响另外一个对象)

深层复制:复制引用对象本身。(沈拷贝,修改一个对象不会影响到论文一个对象)

 


补充:浅议浅拷贝和深拷贝

我认为浅拷贝是一个不喜欢思考的懒汉,而深拷贝则是一个思维严谨,喜欢思考的人。对于懒汉来说,虽然给了他任务,但是他总是想尽量的少做一些事情,所以很多时候做出来的东西就是只看到了表面,不会去思考对不对。 

 

structX{intx;inty;};

对于懒汉来说,他很直白的看到了x,看到了y,然后就拷贝xy,然后就不管了,反正我完成我的拷贝了,至于对不对,我不管。

而一旦有了引用或者指针,事情就不一样了

structX{intx;inty;int*p;};

懒汉依然只是直接表面级别的拷贝,于是拷贝x, y , p,但是他没有思考接下来的事情对不对。对于指针或者引用来说,若是只是拷贝表面,那么拷贝后的物体的指针也和原来的指针指向的是同一个对象,所以虽然目的想完成一个完美的克隆体,但是却发现克隆体和原来的物体中间还有一根线连着,没有完美的分离。

 

int*p=newint(47);int*q=p;

qp都是指向一个物体一样。

那么如果原来的物体销毁了,但是现在拷贝的物体还在,那么这时候你拷贝后的物体的成员指针就是一个悬挂指针,指向了不再存在的物体,那么你访问的话,那就不知道会发生什么了。

 

而对于深拷贝,这一个勤奋的人,他不会只做表面,他会把每一个细节都照顾好。于是,当他遇到指针的时候,他会知道new出来一块新的内存,然后把原来指针指向的值拿过来,这样才是真正的完成了克隆体和原来的物体的完美分离,如果物体比作人的话,那么原来的人的每一根毛细血管都被完美的拷贝了过来,而绝非只是表面。所以,这样的代价会比浅拷贝耗费的精力更大,付出的努力更多,但是是值得的。当原来的物体销毁后,克隆体也可以活的很好。

然而事实上是这个世界上大多都是懒汉,包括编程的人,编译器等,所以默认的行为都是浅拷贝,于是有时候你需要做一个勤奋的人,让事情做正确,自己去完成深拷贝所需要的事情。

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值