您的位置:首页 > 编程语言 > C语言/C++

C++基础知识点总结五

2017-10-14 21:59 267 查看
虚继承:虚函数继承和虚继承是两个不同的概念,



如图,D继承B和C,B和C同时继承A,那么图4中在类D中会出现两次A,为了节省空间,将B和C对A的继承定义为虚拟继承,A就成了虚拟基类

class A;
class B::public virtual A;
class C:public virtual A;
class D :public B,public C;
如果是子类虚继承父类,那么sizeof(子类)得到的值=子类的vptr指向自己的虚函数表的指针(4字节)+子类本来的成员所占空间的大小+sizeof(父类)
因此虚继承算sizeof(类)得到的结果和直接继承不一样,看清继承的时候是直接继承还是虚继承

例题一 

class A{
char k[3];
public:
virtual void aa(){};
};
class B :public virtual A{
char j[3];
public:
virtual void bb(){};
};
class C :public virtual B{
char i[3];
public:
virtual void cc(){};
};
如果是直接继承,求sizeof(A),sizeof(B),sizeof(C)

如果是如上的虚继承,求sizeof(A),sizeof(B),sizeof(C)

答:

直接继承:public

A中char k[3]大小为3,做一次数据对齐后(编译器以4倍数为对齐单位)char k[3]所占大小为4,加上vptr指针,sizeof(A)为8

B中vptr为4字节,加上自己的char j[3]为4字节,加上A中成员变量大小为char k[3]为4字节,sizeof(B)为12

C中vptr=4+自己类中成员变量大小,加上B和A中成员变量大小,为16

如果是虚继承:public virtual

A中vptr=4+成员大小=8

B中vptr=4+B中成员大小(4)+sizeof(A)=16

C中vptr+C中成员大小+sizeof(B)=24

在c++中虚指针指的就是vptr,指向虚函数表的那个指针

在多重继承中,如果C继承A和B,如果出现相同的函数foo()如何明确子类用的哪个父类的foo()?

答:C.A::foo()和C.B::foo()来代表从A类中继承的foo函数和从B类中继承的foo函数

问:如果鸟是可以飞的,那么鸵鸟是鸟吗?鸵鸟如何继承鸟类?

答:可以采用组合的办法,在鸵鸟类中定义和鸟类公有的方法,比如在鸵鸟类的eat方法调用鸟类.eat()即可

在c++继承中注意的是calss Derive:Base{} 如果不指定public,默认的是私有继承,所以应该写继承的时候class Derive:public Base中public是必不可少的

c++的Shape类中virtual void Draw()=0;      加上=0表示它是个纯虚函数,该类不能实例化一个对象,Shape s;都是不可以的

32位机器用十六进制表示的话,那么4为表示一位,那么要有八位。

int有4字节,1个字节有8位,因为是16进制,所以在32位机器下4位二进制数表示1个16进制,因此32位机器,用16进制表示是8位,因为1字节是8位,用16进制表示是2位。

因此一个char类型的数在内存中表示使用2位表示。int类型4个字节,用8位表示。

例题:在32位x86平台下,char a[11] = {100,999,3,4,5,6,7,8,9};    int *p = (int*)a;   那么*p是什么,*(p+1)是什么

解析:


注意的是,在char数组中存放的是10进制的,但是在内存中存放的是16进制,我们需要将10进制转16进制,先必须转成2进制,4位看成一位转成16进制

上图是实际内存的存放,我们先来看下在char数组中存放的100是按照10进制,需要转成16进制,怎么转呢?

2的0次方=1,   2的1次方=2, 2的2次方=4,  2的3次方=8,  2的4次方16, 2的5次方为32,  2的6次方为 64.

100-64=36-32-4=0

因此

128      64      32     16      8     4    2     1

  0          1         1      0        0     1    0      0

表示成二进制为0110 0100,转成16进制为需要把4位看做1位。0110=6   0100 =4  因此用16进制表示为64.

好了,因为a数组保存char类型,一个char为1字节,1字节8位二进制,16进制4位二进制表示1位,因此我们看到1个char中内存是用2位表示的

这题有个细节将char类型的指针转成了int指针。因为int类型是4字节。有8位,因此输出的*p是8位,p的起始地址为a的地址,

因此*p输出0x0403e764

p+1中p为int类型指针因此向后移动4字节

*(p+1)输出为0x08070605

做笔试题的时候一定要注意类型的转换,指针的类型

这里有个细节,我们看到a我分配了11个字节内存,实际我只是初始化了9个数据,那么后面2个内存使用00来保存的,告诉我们分配了多少空间都是按照00来保存,没有分配空间内存中显示cc

c++中STL容器

C++中有两种类型的容器:顺序容器和关联容器。

顺序容器主要有vector、list、deque等。其中vector表示一段连续的内存,基于数组实现,相当于一个动态的数组,当程序员无法知道自己需要的数组的规模多大时,用其来解决问题可以达到最大节约空间的目的.list表示非连续的内存,基于链表实现,deque与vector类似,但是对首元素提供插入和删除的双向支持。

关联容器主要有map和set。map是key-value形式,set是单值。map和set只能存放唯一的key,multimap和multiset可以存放多个相同的key。

容器类自动申请和释放内存,因此无需new和delete操作。

几种标准的非STL容器,包括数组、bitset、valarray、stack、queue和priority_queue。

一、vector

内部数据结构:数组。

 vector<int>
vec1;    //默认初始化,vec1为空

vec1.push_back(100);            //添加元素

vec1.pop_back();              //删除末尾元素

二、list

List是stl实现的双向链表,与 向量(vectors)相比, 它允许快速的插入和删除,但是随机访问却比较慢。需要添加头文件list

三、deque

内部数据结构:数组。

deque容器类与vector类似,支持随机访问和快速插入删除,它在容器中某一位置上的操作所花费的是线性时间。与vector不同的是,deque还支持从开始端插入数据:push_front()。其余类似vector操作方法的使用。

四、map

C++中map容器提供一个键值对(key/value)容器,Map会根据key自动排序。

五、set

set的含义是集合,它是一个有序的容器,里面的元素都是排序好的,支持插入,删除,查找等操作,就像一个集合一样。所有的操作的都是严格在logn时间之内完成,效率非常高。set和multiset的区别是:set插入的元素不能相同,但是multiset可以相同。Set默认自动排序。使用方法类似list。
六  queue(队列)
可以使用数组也可以使用链表来实现。不能遍历整个queue

c++中防止头文件被重复包含的方法

第一种方法  

有一个很方便的宏: #pragma once,加入#pragma once后,编译器在打开或读取第一个#include 模块后,就不会再打开或读取随后出现的相同#include 模块.

第二种方法

用条件编译语句来实现:
#ifndef "XX_H"

#define "XX_H"

<头文件定义正文>
#endif
实际应用:
有下面的自定义结构体,定义在sample.h中。

typedef struct sample{
int trueNumber;
double feature[13];
}SAMPLE;


类A,类B都#include<sample.h>,主程序都调用了类A,类B;就会出现

error C2011: ”sample” : ”struct” type redefinition
解决方法:写上宏定义:
#ifndef sample_H_H
#define sample_H_H
typedef struct sample{
int trueClass;
double feature[13];
}SAMPLE;
#endif


也可以这样写
#if !define sample_H_H
#define sample_H_H
typedef struct sample{
int trueClass;
double feature[13];
}SAMPLE;
#endif
意思是:

if(宏sample_H_H,没有被定义过)

{

      定义宏sample_H_H

      ……..(执行)other code

}

实际上sample_H_H作为一个标记而存在

自定义一个类时,在所有的头文件中都应用这组宏处理

注意是#ifndef不是#ifdef

#ifndef 标记名(常以类名_H_H,两个标记名要相同)

#define 标记名

//你原来的文件内容

#endif

memcpy的源码:

void *memcpy1(void *desc,const void * src,size_t size)
{
if((desc == NULL) && (src == NULL))
{
return NULL;
}
unsigned char *desc1 = (unsigned char*)desc;    //将指针都转成char类型防止类型不匹配
unsigned char *src1 = (unsigned char*)src;
while(size-- >0)
{
*desc1 = *src1;
desc1++;
src1++;
}
return desc;
}
char dest[1024] = "12345666";//{0};
 const char src[5] = "3333";
               那么拷贝的时候,如果用memcpy1(dest,src,sizeof(src));则printf(dest);出来是3333
               如果memcpy1(dest,src,4);则printf(dest);出来是33335666;因为上面的sizeof(src),包含'/0',所以拷贝过去的字符串以'/0'
               结束,就只有3333,而如果传4个字符,'/0'是第五个字符,那就遇到dest[1024] 的'/0'结束,所以是33335666
               字符串的'/0'问题一定要注意啊!!!

strcpy的源码:

char * strcpy(char *dst,const char *src)   //[1]
{
assert(dst != NULL && src != NULL);    //[2]

char *ret = dst;  //[3]

while ((*dst++=*src++)!='\0'); //[4]

return ret;
}
这里注意以下几点:

1 传入的src防止被修改,所以要用const来修饰

2  代码的健壮性,当传入的指针是空的时候,要报错,这里用assert(断言),它的作用是判断一个表达式,如果结果为假,输出诊断消息并中止程序,也可以这样:

if ((strDest==NULL)||(strSrc==NULL)) //[1] 
  throw "Invalid argument(s)";

3  返回目标地址

忘记保存原始的dst值,如果不保存des的值,那么des++的时候地址已经改变了,因此返回des不是传入的des的地址

4  '\0'

循环写成while (*dst++=*src++);明显是错误的。

循环写成while (*src!='\0') *dst++=*src++;

循环体结束后,dst字符串的末尾没有正确地加上'\0'。

strcpy的内存重叠情况:

char s[10]="hello";

//strcpy(s+1, s); //应返回hhello,但实际会报错,因为dst与src重叠了,把'\0'覆盖了

因为s后一位的值是前一位,实际输出s为“hhhhh.....”,栈溢出报错,把\0都覆盖了,

所谓重叠,就是字符串中未处理部分被处理部分覆盖了,memcpy函数自带内存重叠检测功能,下面给出memcpy的实现my_memcpy。
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;
}
此段摘自http://www.cnblogs.com/chenyg32/
可以看到,先判断输出的地址是否在输入字符串地址长度范围内,如果在,就从高往低向后进行复制

strcpy与memcpy的区别:

1、复制的内容不同。strcpy只能复制字符串,而memcpy可以复制任意内容,例如字符数组、整型、结构体、类等。
2、复制的方法不同。strcpy不需要指定长度,它遇到被复制字符的串结束符"\0"才结束,所以容易溢出。memcpy则是根据其第3个参数决定复制的长度。

c++中32位struct的sizeof算法:

1,先计算有多大

2,最终为内部最大成员长度的整数倍

typedef  struct bb {
int  id;
double weight;
float  height;
}BB;
typedef struct aa {
char name[2];
int  id;
double  score;
short  grade;
BB  b;
}AA;
sizeof(BB)=24;

sizeof(AA)=48,因为加上bb结构体又是24所以总共48

重载:overload

覆盖:override

运行时绑定和编译时绑定:

编译时绑定(静态绑定):在程序执行前,编译器就知道要调用哪个方法

运行时绑定(动态绑定):在编译阶段不知道调用哪个方法,知道运行的时候才知道

重载,包括静态方法重载和普通方法重载,静态方法重载是通过类名.方法调用,因为该方法在类中声明为static所以是静态绑定,普通方法重载是new出对象,再对象.方法来实现,因为可能有多态,所以是动态绑定

覆盖,也即重写,子类覆写了父类的方法,有多态,肯定是动态绑定塞。

判断:指针p指向一个对象,则sizeof(p)的值是4,sizeof(*p)的值是p指向对象的实际大小

答案:

笔试题1

char s[]=“12345\t\n\0abcd\0”,求strlen[s] = 7, sizeof[s] = 14.

strlen 是函数,运行时才能确定,所以它只计算了12345\t\n   

而sizeof 是操作符,它包括所有的字符12345\t\n\0abcd\0,还要加一个结束字符,所以为13+1 = 14,这里容易犯错,我以为是13没想到最后的\0后面还要加上\0

int anTest[5][10]; int n1=&anTest[4]-&anTest[0], n2=&anTest[3][1]-&anTest[1][3]; 则n1=____4___  ,n2= ______18____

这题一开始想的是160和17,结果答案为4和18,对于指针的相加减是加减后乘除该指针类型的大小,在二维数组中地址相加减是加减运算后乘除一个单位的大小,&anTest[0]表示横轴的10个数,单位是10个int类型,

所以&anTest[4] - &anTest[0]=(4字节int大小乘以10个元素乘以4个横轴)除以(4字节int大小乘以10个元素)=4

&anTest[4][0] - &anTest[0][0]=40
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: