@dantangfan
2015-01-21T17:12:59.000000Z
字数 26052
阅读 2337
void printLast10Lines(char *fileName){
const int k=10;
ifstream file(filename);
string L[k];
int size = 0;
while(file.good()){
getline(file,L(size%k));
size++;
}
int start = size>k ? (size%k) : 0;
int count = min(k,size);
for(int i=0;i<count;i++)
cout<<L[(start+i)%k]<<endl;
}
class Shape{
public:
int edeg_length;
virtual int circumference(){
cout << “Circumference of Base Class\n”;
return 0;
}
};
class Triangle: public Shape{
public:
int circumference(){
cout << “Circumference of Triangle Class\n”;
return 3*edeg_length;
}
};
int main(int argc,char **argv){
Shape *X = new Shape();
X->cricumference();//Base Class;
Shape *Y = new Triangle();
Y->circumference();//Triangle Class
}
Struct Test{
char *ptr;
};
void shallow_Copy(Test &src,Test &dest){
dest.ptr = src.ptr;
}
void deep_Copy(Test &src,Test &dest){
dest.ptr = (char *)malloc(strlen(src.ptr)+1);
strcpy(dest.ptr,src.ptr);
}
实际开发中,大部分情况都应该使用深拷贝。
extern “C”
{
#include “myfile.h”
void foo (int x, int y);
}
这就告诉C++编译译器,函数foo 是个C连接,应该到库中找名字_foo而不是找_foo_int_int。C++编译器开发商已经对C标准库的头文件作了extern“C”处理,所以我们可以用#include 直接引用这些头文件。
C++语言支持函数重载,C语言不支持函数重载。C++提供了C连接交换指定符号extern “C”
解决名字匹配问题
首先,作为extern是C/C++语言中表明函数和全局变量作用范围(可见性)的关键字,该关键字告诉编译器,其声明的函数和变量可以在本模块或其它模块中使用。
通常,在模块的头文件中对本模块提供给其它模块引用的函数和全局变量以关键字extern声明。例如,如果模块B欲引用该模块A中定义的全局变量和函数时只需包含模块A的头文件即可。这样,模块B中调用模块A中的函数时,在编译阶段,模块B虽然找不到该函数,但是并不会报错;它会在连接阶段中从模块A编译生成的目标代码中找到此函数
exten "C"是连接申明(linkage declaration),被extern "C"修饰的变量和函数是按照C语言方式编译和连接的,来看看C++中对类似C的函数是怎样编译的:
作为一种面向对象的语言,C++支持函数重载,而过程式语言C则不支持。函数被C++编译后在符号库中的名字与C语言的不同。例如,假设某个函数的原型为:
void foo( int x, int y );
该函数被C编译器编译后在符号库中的名字为_foo,而C++编译器则会产生像_foo_int_int之类的名字(不同的编译器可能生成的名字不同,但是都采用了相同的机制,生成的新名字称为“mangled name”)。
_foo_int_int 这样的名字包含了函数名、函数参数数量及类型信息,C++就是靠这种机制来实现函数重载的。例如,在C++中,函数void foo( int x, int y )与void foo( int x, float y )编译生成的符号是不相同的,后者为_foo_int_float。
同样地,C++中的变量除支持局部变量外,还支持类成员变量和全局变量。用户所编写程序的类成员变量可能与全局变量同名,我们以"."来区分。而本质上,编译器在进行编译时,与函数的处理相似,也为类中的变量取了一个独一无二的名字,这个名字与用户程序中同名的全局变量名字不同。
未加extern "C"声明时的连接方式
假设在C++中,模块A的头文件如下:
// 模块A头文件 moduleA.h
#ifndef MODULE_A_H
#define MODULE_A_H
int foo( int x, int y );
#endif
在模块B中引用该函数:
// 模块B实现文件 moduleB.cpp
#i nclude "moduleA.h"
foo(2,3);
加exten "C"声明后的编译和连接方式
加extern "C"声明后,模块A的头文件变为:
// 模块A头文件 moduleA.h
#ifndef MODULE_A_H
#define MODULE_A_H
extern "C" int foo( int x, int y );
#endif
在模块B的实现文件中仍然调用foo( 2,3 ),其结果是:
(1)模块A编译生成foo的目标代码时,没有对其名字进行特殊处理,采用了C语言的方式;
(2)连接器在为模块B的目标代码寻找foo(2,3)调用时,寻找的是未经修改的符号名_foo。
如果在模块A中函数声明了foo为extern "C"类型,而模块B中包含的是extern int foo( int x, int y ) ,则模块B找不到模块A中的函数;反之亦然。
所以,可以用一句话概括extern “C”这个声明的真实目的(任何语言中的任何语法特性的诞生都不是随意而为的,来源于真实世界的需求驱动。我们在思考问题时,不能只停留在这个语言是怎么做的,还要问一问它为什么要这么做,动机是什么,这样我们可以更深入地理解许多问题):实现C++与C及其它语言的混合编程。
明白了C++中extern "C"的设立动机,我们下面来具体分析extern "C"通常的使用技巧:
extern "C"的惯用法
(1)在C++中引用C语言中的函数和变量,在包含C语言头文件(假设为cExample.h)时,需进行下列处理:
extern "C"
{
#i nclude "cExample.h"
}
而在C语言的头文件中,对其外部函数只能指定为extern类型,C语言中不支持extern "C"声明,在.c文件中包含了extern "C"时会出现编译语法错误。
C++引用C函数例子工程中包含的三个文件的源代码如下:
/* c语言头文件:cExample.h */
#ifndef C_EXAMPLE_H
#define C_EXAMPLE_H
extern int add(int x,int y);
#endif
/* c语言实现文件:cExample.c */
#i nclude "cExample.h"
int add( int x, int y )
{
return x + y;
}
// c++实现文件,调用add:cppFile.cpp
extern "C"
{
#i nclude "cExample.h"
}
int main(int argc, char* argv[])
{
add(2,3);
return 0;
}
如果C++调用一个C语言编写的.DLL时,当包括.DLL的头文件或声明接口函数时,应加extern "C" { }。
(2)在C中引用C++语言中的函数和变量时,C++的头文件需添加extern "C",但是在C语言中不能直接引用声明了extern "C"的该头文件,应该仅将C文件中将C++中定义的extern "C"函数声明为extern类型。
C引用C++函数例子工程中包含的三个文件的源代码如下:
//C++头文件 cppExample.h
#ifndef CPP_EXAMPLE_H
#define CPP_EXAMPLE_H
extern "C" int add( int x, int y );
#endif
//C++实现文件 cppExample.cpp
#i nclude "cppExample.h"
int add( int x, int y )
{
return x + y;
}
/* C实现文件 cFile.c
/* 这样会编译出错:#i nclude "cExample.h" */
#include <stdio.h>
#include "cppExample.h"
extern int add(int x, int y);
int main( int argc, char* argv[] )
{
printf("%d\n",add( 2, 3 ));
return 0;
}
# include <iostream.h>
void output( int x); // 函数声明
void output( float x); // 函数声明
void output( int x)
{
cout << " output int " << x << endl ;
}
void output( float x)
{
cout << " output float " << x << endl ;
}
void main(void)
{
int x = 1;
float y = 1.0;
output(x); // output int 1
output(y); // output float 1
output(1); // output int 1
// output(0.5); // error! ambiguous call, 因为自动类型转换
output(int(0.5)); // output int 0
output(float(0.5)); // output float 0.5
}
第一个output函数的参数是int类型,第二个output函数的参数是float类型。由于数字本身没有类型,将数字当作参数时将自动进行类型转换(称为隐式类型转换)。语句output(0.5)将产生编译错误,因为编译器不知道该将0.5转换成int还是float类型的参数。
关于函数的重载、覆盖、隐藏
成员函数被重载的特征:
(1)相同的范围(在同一个类中);
(2)函数名字相同;
(3)参数不同;
(4)virtual关键字可有可无。
覆盖是指派生类函数覆盖基类函数,特征是:
(1)不同的范围(分别位于派生类与基类);
(2)函数名字相同;
(3)参数相同;
(4)基类函数必须有virtual关键字。
前两者还很容易理解。但是函数的隐藏就不容易理解了。是这样说的:
2)令人迷惑的隐藏规则
本来仅仅区别重载与覆盖并不算困难,但是C++的隐藏规则使问题复杂性陡然增加。
这里“隐藏”是指派生类的函数屏蔽了与其同名的基类函数,规则如下:
(1)如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual关键字,基类的函数将被隐藏(注意别与重载混淆)。
(2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)。
函数的缺省值,只能出现在声明中,不能出现在函数定义体中
void foo(int x, int y=0, int z=0);正确
void foo(int x, int y=0, int z=0){}错误
内存对齐(主要在struct中)。
如下
struct test1{
int a;
int b;
char c;
}t1;
sizeof(t1) = 12;
struct test2{
int a:2;
int b;
char c;
}t2;
sizeof(t2) = 12;
struct test3{
int a:2;
char b;
int c;
}t3;
sizeof(t3) = 8;
class test{
public:
test & opereate = (const test & rhs);
…
private:
string *str;
}
常规情况下,我们总是实现一份不安全的赋值
test & test::operator= (const test & rhs){
delete str;
str = new string(*rhs.str);
return *this;
}//当rhs==*this的时候就会出现错误
那么我们需要改进这个错误
test & test::operator=(const test& rhs){
if(this==&rhs) return *this;
delete str;
str = new string(*rhs.str);
return *this;
}//但是这样的情况还是存在问题,比如说new操作出错(虽然这样的情况很少发生),内存不足
那么我们就不能把原来的数据丢失了
test &test::operator=(const test &rhs){
if(this==&rhs) return *this;
string *old = str;
str = new string(*rhs.str);
delete old;
return *this;
}
同上面,我们就可以使用一个叫做copy-and-swap的方法来简化代码.用一个副本来保存需要赋值的数据,然后与被赋值的交换。
test & test::operator= (const test& rhs){
test temp(rhs);
swap(temp);
return *this;
}
class Base{
public:
virtual void mf1() = 0;
virtual void mf1(int);
virtual void mf2();
void mf3();
void mf3(int);
private:
int x;
};
class derived:public Base{
public:
virtual void mf1();
void mf3();
void mf4();
};
以作用域为基础的“名称遮掩规则”并没有改变,因此base class内所有名为mf1和mf3的 函数都被derived class内的mf1和mf3函数遮掩掉了。
Derived d;
int x;
d.mf1();//ok,调用derived::mf1
d.mf1(x);//wrong!, derived:mf1掩盖了Base::mf1;
d.mf2();//ok,Base::mf2
d.mf3();//ok,derived::mf3;
d.mf3(x);//wrong!!,derived::mf3掩盖了Base::mf3
如我们看到的,规则都适用,即使参数不同也使用,不管是不是virtual都适用。但不幸的是我们经常需要重写哪些重名函数,这个时候有一个办法能及解决上面问题,使用using让base的函数变得可见
class derived:public Base{
public:
using Base::mf1;//让Baee中名字叫的mf1和mf3的所有东西
using Base::mf3;//在derived中变得可见并且是public
virtual void mf1();
void mf3();
void mf4();
};
那么现在调用就可以正常运行了,不过mf1和mf3都是属于derived类了
Derived d;
int x;
d.mf1();//ok,调用derived::mf1
d.mf1(x);//ok, derived:mf1
d.mf2();//ok,Base::mf2
d.mf3();//ok,derived::mf3;
d.mf3(x);//ok,derived::mf3
注意:虽然我们这里说的很详细,但是我们应该遵循一个原则“绝不重新定义继承而来的non-virtual 函数”。因为在不适用vurtual的时候,不管是基类指针还是本身,他都只会调用本身类的函数。
虚基类与抽象类
虚基类是相对于它的派生类而言的,它本身可以是一个普通的类。
只有它的派生类虚继承它的时候,它才称作虚基类,如果没有虚继承的话,就称为基类。比如类B虚继承于类A,那类A就称作类B的虚基类,如果没有虚继承,那类B就只是类A的基类。
虚继承主要用于一个类继承多个类的情况,避免重复继承同一个类两次或多次。
例如 由类A派生类B和类C,类D又同时继承类B和类C,这时候类D就要用虚继承的方式避免重复继承类A两次。
而抽象类是指带有有一个或一个以上的纯虚函数的类。抽象类一般值用于继承,不能定义类对象,但可以定义类指针和引用。
编写string.h中的某些函数
注意!!!最大的问题就是判断是否是NULL,是否有重叠!!是否有返回值!!返回值的类型!!
int strstr(char *str, char *substr){
if ( str==NULL || substr==NULL )return -1;
int lenstr = strlen(str);
int lensub = strlen(substr);
if( lenstr < lensub ) return -1;
int len = lenstr – lensub;
int i ,j;
for(i = 0;i<len;i++){
for (j=0;j<lensub;j++)
if(str[i+j]!=substr[j]) break;
if(j==lensub)
return i+1;
}
return -1;
}
char *strcpy(char *dest, char *src){
if(dest==src) return dest;
assert( (dest!=NULL) && (src!=NULL) );
char *address = dest;
while( (*dest++=*src++)!='\0' );
return address;
}
int strcmp(const char *s, const char *t){
assert(s!=NULL&&t!=NULL);
while( *s && *t && *s==*t ){
s++;
t++;
}
return (*s-*t);
}
int strlen(const char *str){
assert( s!=NUL );
int len = 0;
while(*s++!='\0') len++;
return len;
}
char *strlwr(char *str){
assert( str!=NULL );
char *s = str;
while(*s!='\0'){
if(*s>'A'&&*s<'Z')
*s+=0x20;
s++;
}
return str;
}
void *memcpy(void *dest, void *src, unsigned int count){
assert( dest!=NULL && src!=NULL );
void *address = dest;
while(count--){
*(char *)dest = *(char *)src;
dest = (char *)dest + 1;
src = (char *)src +1;
}
return address;
}
void *memset(void *str, int c, unsigned int count){
assert( str!=NULL );
void *s = str;
while(count--){
*(char *)s = (char)c;
s = (char *)s+1;
}
return str;
}
int i=0;
while(1){
printf(“*”);
scanf(“%d”,&i);
if(i==1) break;
}
当我们在上面输入任意一个不是%d的字符的时候,屏幕就会不断的输出****。这个时候我们需要做的就是手动去清除缓冲区(也就是把缓冲区里面的字符读取完)
int i=0;
char c;
while(1){
printf(“*”);
scanf(“%d”,&i);
while( (c=getchar())!='\n' && c!=EOF );
if(i==1) break;
}
B.scanf在读取字符串的时候,遇到空白字符就会自动停止当前的匹配。这样就会导致一个字符串读取不完整.
我们需要做的就是利用其他函数来代替scanf输入
char buf[1024];
fgets(buf, sizeof(buf),stdin);
if(sscanf(buf,”%d %c %d”,&a,&b,&c)==3)
printf(“OK\n”);
总结:
- scanf读取单个字符的时候任何字符都不放弃
- 读取数字和字符串的时候,空格等分隔符会被当成结束
- 读取成功会清空缓冲区,不成功不清空
- 键盘的输入都被保存到缓冲区中直到输入回车。输入函数会直接读取缓冲区,如果空则等待输入,不空不等待输入。
顺便回顾一下不那么常用的sscanf,sprintf,fscanf,fprintf,fgets,fputs
虚函数浅析。
#include <iostream>
using namespace std;
class A{
public:virtual void p()
{
cout << "A" << endl;
}
};
class B : public A
{
public:virtual void p()
{ cout << "B" << endl;
}
};
int main()
{
A * a = new A;
A * b = new B;
a->p();
b->p();
delete a;
delete b;
return 0;
}
答案将输出AB
但是我们把virtual关键字去掉之后
class A
{
public:
void p()
{
cout << "A" << endl;
}
};
class B : public A
{
public:
void p()
{
cout << "B" << endl;
}
};
答案就成了AA
在构造一个类的对象时,如果它有基类,那么首先将构造基类的对象,然后 才构造派生类自己的对象
原因是A *b = new B(B *b = new B就会调用B的)构造了派生类B的对象,但是由于B是A的派生类,所以要先构造A对象然后再构造B对象。但是由于当程序中的函数是非虚函数的时候,B的函数p()的调用在编译时已经静态确定了,所以基类指针不管是指向哪里,都将调用基类函数,只要调用的函数不是虚函数就直接无视。
inline
C++四种强制类型转化
c语言中直接type b = (type)a;
C++中可以使用这种方式:type b = 强制类型转换符(a)
double d = static_cast<double>(n);
void *p = static_cast<void *>(ptr);
class A{};
class B:public A{};
A *a = new A; B *b; b = static_cast<B *>(a);//不能子类转父类
- dynamic_cast:static_cast没有dynamic_cast安全,因为static不做类型检查,对于一个模糊指针用static_cast不会报错,但是用dynamic就会报错。dynamic_cast仅仅对指针和引用有效。
- const_cast:去掉类型的const或volalitle属性。不能在不同的类型间转换
例子:
int a=3;const int *b=&a; int *c = const_cast(b); *c=4;这个时候a变成了4。显然这样做是没有什么意义的,比如我可以直接int *c = &a;同样可以修改a的值。所以,我觉得这个关键字的用法应该这样*const_cast(b) = 5;
简单解释多态
将父对象设置成和一个或多个他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给他的子对象的特性以不同的方式运作。简单说:允许将子类类型的指针赋值给父类类型的指针。多台可以简单的概括成“一种接口 多种实现”,在程序运行的时候才决定调用的函数,也就是动态绑定。
多态的作用主要有两个:1隐藏实现的细节,使得代码能模块化,实现代码重用;2接口重用,为了类在继承和派生的时候,保证使用家族中任意类的某一属性时的正确调用。
简单说说const,volatile,mutable的用法
class AAA{
public:
void func1(){};
void func2() const{};
};
const AAA obj;
obj.func1();//错误
obj.func2();//正确
const修饰的数据成员,值在某个对象的生存空间是常量。不同实例的const成员值可以不一样,所以不能呢个在类声明中初始化const
class AAA{
const int size=100;//错误
};
它只能在初始化列表中初始化
class AAA{
public:
AAA(int n):size(n){}
private:
const int size;
};
也可以使用enum代替
class AAA{
private:
enum{size=100};
}
const 修饰成员函数,一般把const放在函数的最后,表示不能修改该对象的成员变量
class AAA{
public:
int haha()const{};
}
const 修饰函数的返回值。除了重载操作符号,其他一般不常用。
- volatile:易变的
volatile的本意是“易变的”,volatile关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改,比如操作系统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。
Mutabled:修饰的变量,就算是函数声明为const,也可以修改mutable修饰的变量。
void get(char *str, int size){
str = (char *)malloc(sizeof(char)*size);
}
这里的str不会编程size大小的数组。因为编译器总是要为函数的每个参数制造临时副本,指针str的副本是_str,编译器使_str=str。如果函数体内的代码修改了_str,就会导致str的内容做相应的修改。这就是指针可以用作输出参数的原因。再上面代码中_str申请了新的内存,只是把_str的内存地址改变了,但是str自己却还没有改变(指针参数只能改变传入指针所指向的内容,而不是地址),所以get()函数不会输出任何东西。事实上,每执行一次函数,就会泄漏一次内存。
上面代码必须改成如下,才能正常执行
void get(char **str,int size){
*p = (char *)malloc(sizeof(char)*size);
}
main(){
char *str;
get(&str,10);//这里必须传入str的地址
}
这很麻烦,也很费解。所以我们通常用更简单的办法来代替这个函数
char *get(int size){
char *str = (char *)malloc(sizeof(char)*size);
return str;
}
20.5 free和delete
需要知道的是,指针p被free掉之后内存地址并不会改变(不是NULL),只是该地址对应的内容变成了垃圾,p就成了传说中的野指针。如果这个时候不把p=NULL,if(p!=NULL)是不能判断正确的
20.6 防止野指针
野指针不是指向NULL,而是指向垃圾。野指针出现一般有两种可能
(1)使用没有初始化的指针。任何指针刚刚被创建的时候都不是NULL
(2)释放之后没有指向NULL
20.7 如何应对内存耗尽
有时候malloc和new找不到足够大的内存的时候,就会分配失败,返回NULL。比如我们在类中定义一个赋值操作的时候就应该防止内存分配不成功导致=操作符失败
test &test::operator=(const test &p){
test temp=new test(p);
swap(temp);
return *this;
}
struct A
{
char a;
};
struct B : A
{
char b;
};
这个时候B是public继承A的。
如果都将上面的struct改成class,那么B是private继承A的。这就是默认的继承访问权限。
(还有一个区别就是有时候template)
#include <stdio.h>
int main() {
char p[] = "\\\bSRE\0abc\\\b\n";
printf("%d %d\n", strlen(p),sizeof(p));
printf("%%s = %s", p);
return 0;
}
输出strlen(p) = 5,sizeof(p) = 13,p=”SRE”
printf(“%d”,printf(“%d”,printf(“%d”,12)));//输出1221;
int a = 1,2;//错误
int a = (1,2);//正确, a=2
#include<iostream>
using namespace std;
struct A
{};//1
struct B
{
char c;
int i;
};//8
struct C
{
int i;
char c;
};//8
struct D
{
char c1;
char c2;
int i;
};//8
struct E
{
int i;
char c1;
char c2;
};//8
struct F
{
char c1;
int i;
char c2;
};//12
union G
{
char c1;
int i;
char c2;
double d;
};//8
class H
{};//1
class I
{
private:
int i;
};//4
class J
{
public:
virtual void display();
};//4
class K
{
private:
int i;
public:
void display();
};//4
class L
{
private:
int i;
public:
virtual void display();
};//8
class M
{
private:
int i;
int j;
public:
virtual void display();
};//12
class N
{
private:
static int i;
};//1
class O
{
private:
static int i;
int j;
};//4
void fun1(char str[20])
{
cout << sizeof(str) << endl;
}
void fun2(char a[10][9])
{
cout << sizeof(a) << endl;
}
int main()
{
char *p1;
cout << sizeof(p1) << endl; // 4
int *p2 = new int[100];
cout << sizeof(p2) << endl; // 4
delete [] p2;
float *p3;
cout << sizeof(p3) << endl; // 4
double ******p4;
cout << sizeof(p4) << endl; // 4
char str[100] = "abcdefg";
fun1(str); // 4 (fun1中的形参str是指针变量)
cout << sizeof(str) << endl; // 100 (str的容量为100)
cout << sizeof(*str) << endl; // 1 (*str是一个字符数据)
cout << strlen(str) << endl; // 7 (字符串str的长度)
char str1[] = "abcdeofg"; //(里面是字符'o')
cout << sizeof(str1) << endl; // 9
cout << strlen(str1) << endl; // 8
char str2[] = "abcde0fg"; //(里面是字符'0',不等于'\0')
cout << sizeof(str2) << endl; // 9
cout << strlen(str2) << endl; // 8
char str3[] = "abcde\0fg";
cout << sizeof(str3) << endl; // 9
cout << strlen(str3) << endl; // 5
char str4[] = {'a', 'b', 'c', 'd', 'e', 'f', 'g'};
cout << sizeof(str4) << endl; // 7
cout << strlen(str4) << endl; // 13 (数值不确定)
char str5[] = {'a', 'b', 'c', 'd', 'e', 'o', 'f', 'g'};
cout << sizeof(str5) << endl; // 8
cout << strlen(str5) << endl; // 21 (数值不确定)
char str6[] = {'a', 'b', 'c', 'd', 'e', '0', 'f', 'g'};
cout << sizeof(str6) << endl; // 8
cout << strlen(str6) << endl; // 29 (数值不确定)
char str7[] = {'a', 'b', 'c', 'd', 'e', '\0', 'f', 'g'};
cout << sizeof(str7) << endl; // 8
cout << strlen(str7) << endl; // 5
char str8[] = {'a', 'b', 'c', 'd', 'e', 0, 'f', 'g'};
cout << sizeof(str8) << endl; // 8
cout << strlen(str8) << endl; // 5
char str9[] = "";
cout << sizeof(str9) << endl; // 1
cout << strlen(str9) << endl; // 0
char a[10][9];
cout << sizeof(a) << endl; // 90
fun2(a); // 4 (fun2中的a为指针变量)
cout << sizeof(A) << endl; // 1 (编译器实现)
cout << sizeof(B) << endl; // 8 (内存对齐)
cout << sizeof(C) << endl; // 8 (内存对齐)
cout << sizeof(D) << endl; // 8 (内存对齐)
cout << sizeof(E) << endl; // 8 (内存对齐)
cout << sizeof(F) << endl; // 12 (内存对齐)
cout << sizeof(G) << endl; // 8 (内存共用)
cout << sizeof(H) << endl; // 1 (编译器实现)
cout << sizeof(I) << endl; // 4
cout << sizeof(J) << endl; // 4 (虚指针)
cout << sizeof(K) << endl; // 4
cout << sizeof(L) << endl; // 8 (虚指针)
cout << sizeof(M) << endl; // 12 (虚指针)
cout << sizeof(N) << endl; // 1 (静态成员变量不专属某一对象)
cout << sizeof(O) << endl; // 4 (静态成员变量不专属某一对象)
return 0;
}
总结:
Sizeof的作用非常简单:求对象或者类型的大小。然而sizeof又非常复杂,它涉及到很多特殊情况,本篇把这些情况分门别类,总结出了sizeof的10个特性:
(0)sizeof是运算符,不是函数;
(1)sizeof不能求得void类型的长度;
(2)sizeof能求得void类型的指针的长度;
(3)sizeof能求得静态分配内存的数组的长度!
(4)sizeof不能求得动态分配的内存的大小!
(5)sizeof不能对不完整的数组求长度;
(6)当表达式作为sizeof的操作数时,它返回表达式的计算结果的类型大小,但是它不对表达式求值!
(7)sizeof可以对函数调用求大小,并且求得的大小等于返回类型的大小,但是不执行函数体!
(8)sizeof求得的结构体(及其对象)的大小并不等于各个数据成员对象的大小之和!
(9)sizeof不能用于求结构体的位域成员的大小,但是可以求得包含位域成员的结构体的大小!
Effective c++笔记
自己看书,略
下面的程序并不见得会输出 hello-std-out,你知道为什么吗?
#include <stdio.h>
#include <unistd.h>
int main()
{
while(1)
{
fprintf(stdout,"hello-std-out");
fprintf(stderr,"hello-std-err");
sleep(1);
}
return 0;
}
参考答案:stdout和stderr是不是同设备描述符。stdout是块设备,stderr则不是。对于块设备,只有当下面几种情况下才会被输入,1)遇到回车,2)缓冲区满,3)flush被调用。而stderr则不会。
#include <stdio.h>
int main()
{
int a = 1,2;
printf("a : %d\n",a);
return 0;
}
参考答案:这个程序会得到编译出错(语法出错),逗号表达式是没错,可是在初始化和变量声明时,逗号并不是逗号表达式的意义。这点要区分,要修改上面这个程序,你需要加上括号: int a = (1,2);
#include <stdio.h>
int main()
{
int i=43;
printf("%d\n",printf("%d",printf("%d",i)));
return 0;
}
参考答案:程序会输出4321,你知道为什么吗?要知道为什么,你需要知道printf的返回值是什么。printf返回值是输出的字符个数。
#include <stdio.h>
int main()
{
float a = 12.5;
printf("%d\n", a);
printf("%d\n", (int)a);
printf("%d\n", *(int *)&a);
return 0;
}
参考答案:
该项程序输出如下所示,
0
12
1095237632
原因是:浮点数是4个字节,12.5f 转成二进制是:01000001010010000000000000000000,十六进制是:0×41480000,十进制是:1095237632。所以,第二和第三个输出相信大家也知道是为什么了。而对于第一个,为什么会输出0,我们需要了解一下float和double的内存布局,如下:
float: 1位符号位(s)、8位指数(e),23位尾数(m,共32位)
double: 1位符号位(s)、11位指数(e),52位尾数(m,共64位)
然后,我们还需要了解一下printf由于类型不匹配,所以,会把float直接转成double,注意,12.5的float和double的内存二进制完全不一样。别忘了在x86芯片下使用是的反字节序,高位字节和低位字位要反过来。所以
float版:0×41480000 (在内存中是:00 00 48 41)
double版:0×4029000000000000 (在内存中是:00 00 00 00 00 00 29 40)
而我们的%d要求是一个4字节的int,对于double的内存布局,我们可以看到前四个字节是00,所以输出自然是0了。
这个示例向我们说明printf并不是类型安全的,这就是为什么C++要引如cout的原因了。
file1.c
int arr[80];
////////////////////
file2.c
extern int *arr;
int main()
{
arr[1] = 100;
printf("%d\n", arr[1]);
return 0;
}
参考答案:该程序可以编译通过,但运行时会出错。为什么呢?原因是,在另一个文件中用 extern int *arr来外部声明一个数组并不能得到实际的期望值,因为他们的类型并不匹配。所以导致指针实际并没有指向那个数组。注意:一个指向数组的指针,并不等于一个数组。修改:extern int arr[]。(参考:ISO C语言 6.5.4.2 节)
32. 请说出下面的程序输出是多少?并解释为什么?(注意,该程序并不会输出 “b is 20″)
#include <stdio.h>
int main()
{
int a=1;
switch(a)
{
int b=20;
case 1:
printf("b is %d\n",b);
break;
default:
printf("b is %d\n",b);
break;
}
return 0;
}
参考答案:该程序在编译时,可能会出现一条warning: unreachable code at beginning of switch statement。我们以为进入switch后,变量b会被初始化,其实并不然,因为switch-case语句会把变量b的初始化直接就跳过了。所以,程序会输出一个随机的内存值。
#include <stdio.h>
int main()
{
char str[80];
printf("Enter the string:");
scanf("%s",str);
printf("You entered:%s\n",str);
return 0;
}
参考答案:本题很简单了。这个程序的潜在问题是,如果用户输入了超过80个长度的字符,那么就会有数组越界的问题了,你的程序很有可以及会crash了。
#include <stdio.h>
int main()
{
int i;
i = 10;
printf("i : %d\n",i);
printf("sizeof(i++) is: %d\n",sizeof(i++));
printf("i : %d\n",i);
return 0;
}
参考答案:如果你觉得输出分别是,10,4,11,那么你就错了,错在了第三个,第一个是10没有什么问题,第二个是4,也没有什么问题,因为是32位机上一个int有4个字节。但是第三个为什么输出的不是11呢?居然还是10?原因是,sizeof不是一个函数,是一个操作符,其求i++的类型的size,这是一件可以在程序运行前(编译时)完全的事情,所以,sizeof(i++)直接就被4给取代了,在运行时也就不会有了i++这个表达式。
#include <stdio.h>
#include <stdlib.h>
#define SIZEOF(arr) (sizeof(arr)/sizeof(arr[0]))
#define PrintInt(expr) printf("%s:%d\n",#expr,(expr))
int main()
{
/* The powers of 10 */
int pot[] = {
0001,
0010,
0100,
1000
};
int i;
for(i=0;i<SIZEOF(pot);i++)
PrintInt(pot[i]);
return 0;
}
参考答案:好吧,如果你对于PrintInt这个宏有问题的话,你可以去看一看《语言的歧义》中的第四个示例。不过,本例的问题不在这里,本例的输出会是:1,8,64,1000,其实很简单了,以C/C++中,以0开头的数字都是八进制的。
#include
#define PrintInt(expr) printf("%s : %dn",#expr,(expr))
int main()
{
int y = 100;
int *p;
p = malloc(sizeof(int));
*p = 10;
y = y/*p; /*dividing y by *p */;
PrintInt(y);
return 0;
}
参考答案:本题输出的是100。为什么呢?问题就出在 y = y/*p;上了,我们本来想的是 y / (*p) ,然而,我们没有加入空格和括号,结果y/p中的 /被解释成了注释的开始。于是,这也是整个恶梦的开始。
37. 下面的输出是什么?
#include <stdio.h>
int main()
{
int i = 6;
if( ((++i < 7) && ( i++/6)) || (++i <= 9))
;
printf("%d\n",i);
return 0;
}
参考答案:本题并不简单的是考前缀++或反缀++,本题主要考的是&&和||的短路求值的问题。所为短路求值:对于(条件1 && 条件2),如果“条件1”是false,那“条件2”的表达式会被忽略了。对于(条件1 || 条件2),如果“条件1”为true,而“条件2”的表达式则被忽略了。所以,我相信你会知道本题的答案是什么了。
#include <stdio.h>
int main()
{
int a=3, b = 5;
printf(&a["Ya!Hello! how is this? %s\n"], &b["junk/super"]);
printf(&a["WHAT%c%c%c %c%c %c !\n"], 1["this"],
2["beauty"],0["tool"],0["is"],3["sensitive"],4["CCCCCC"]);
return 0;
}
参考答案:
本例是合法的,输出如下:
Hello! how is this? super
That is C !
本例主要展示了一种另类的用法。下面的两种用法是相同的:
“hello”[2]
2["hello"]
如果你知道:a[i] 其实就是 *(a+i)也就是 *(i+a),所以如果写成 i[a] 应该也不难理解了。
#include <stdio.h>
int main()
{
char dummy[80];
printf("Enter a string:\n");
scanf("%[^r]",dummy);
printf("%s\n",dummy);
return 0;
}
参考答案:本例的输出是“Hello, Wo”,scanf中的”%[^r]“是从中作梗的东西。意思是遇到字符r就结束了。
#include <stdio.h>
#define PrintInt(expr) printf("%s : %d\n",#expr,(expr))
int FiveTimes(int a)
{
int t;
t = a<<2 + a;
return t;
}
int main()
{
int a = 1, b = 2,c = 3;
PrintInt(FiveTimes(a));
PrintInt(FiveTimes(b));
PrintInt(FiveTimes(c));
return 0;
}
参考答案:本题的问题在于函数FiveTimes中的表达式“t = a<<2 + a;”,对于a<<2这个位操作,优先级要比加法要低,所以这个表达式就成了“t = a << (2+a)”,于是我们就得不到我们想要的值。该程序修正如下:
int FiveTimes(int a)
{
int t;
t = (a<<2) + a;
return t;
}
a=a^b;b=a^b;a=a^b;
a=a+b;b=a-b;a=a-b;
还可以用memcpy()
class A{
private:
int value;
public:
A(int n){value=n;}
A(A another){value = another.value;}
}
代码中的拷贝构造函数有问题A(A another),参数的形参只能是本类类型的引用,标准的应该是const A another
构造函数,静态函数和内联函数可不可以是虚函数。
虚构函数必须是虚的吗?
不是,如果是基类就必须是。虚函数形式的析构函数做要是为了在实现多态的时不造成内存泄漏(调用顺序)比如下面程序B的析构函数不能被调用,但是A写成虚后就可以调用了。
#include <iostream>
using namespace std;
class A
{
public:
A() {cout<<"A Constructor\n";}
~A() {cout<<"A Destructor\n";}
};
class B:public A
{
public:
B() { cout<<"B Constructor\n";}
~B() { cout<<"B Destructor\n";}
};
int main()
{
A *pa=new B();
delete pa;
return 0;
}
class FinalClass
{
public:
static FinalClass* GetInstance()
{
return new FinalClass;
}
static void DeleteInstance(FinalClass* pInstance)
{
delete pInstance;
pInstance = NULL;
}
private:
FinalClass() {} //私有的构造函数
~FinalClass() {} //私有的析构函数
};
//Single.h定义
#pragma once
class Singleton
{
public:
static Singleton* GetInstance();
private:
Singleton();
~Singleton();
static Singleton *singleton;
};
//Singleton.cpp定义
Singleton* Singleton::singleton = NULL; //静态成员初始化
Singleton::Singleton()
{
}
Singleton::~Singleton()
{
}
Singleton* Singleton::GetInstance()
{
if(singleton == NULL)
singleton = new Singleton();
return singleton;
}
方法二:利用局部静态变量只能初始化一次
class Singleton
{
private:
Singleton(){}
public:
static Singleton* GetInstance()
{
static Singleton singleton; //局部静态变量
return &singleton;
}
};
20.问题20: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类型,以使得其返回值不为"左值"。
数组与指针的区别。
解答:摘自《C专家编程》。
(1)数组:保存数据;指针:保存数据的地址。
(2)数组:直接访问数据;指针:间接访问数据,首先取得指针的内容,把它作为地址,然后从地址提取数据
(3)数组:通常用于存储固定数目前数据类型相同的元素;指针:通常用于动态数据结构
(4)数组:隐式分配和删除;指针:相关的函数为malloc(),free()。
(5)数组:自身即为数据名;指针,通常指向匿名数据。
静态多态和动态多态
程序输出
union{
int a;
char b[3];
}u;
memset(&u,0,sizeof(u));
u.b[0]=1;
u.a的输出是多少。
首先union内存是按照最大的那个存放于是sizeof(u)=4;然后内存中有四个位置分别存放二个16进制位,这里的b[0]刚好放在第三个上,内存显示为0x00,0x00,0x01,0x00,按照小段规则,a为65536
24. int/float/bool/指针 和0比较大小
int:if(0==a)
float:const float eps = 0.000001; if(a>=-eps&&a<=eps)
bool:if(a)
pointer:if(NULL==a)
class Base{
virtual void foo() = 0;
virtual void bar() = 0
}
class Der1:public Base{
virtual void foo() override{}
virtual void bar() final {}
virtual void baz() final {}//编译失败,override限定必须是重载的虚函数
}
class Der2:public Der1{
virtual void foo() override{}
virtual void bar() override{}//错误,已经final了,不能再继承了。
}
main(){
int n=[](int x, int y){return x+y;}(4,5);
}
可以看到,我们通过函数后面的()传递参数
例2
main(){
auto f=[](int x, int y){return x+y;};
f(4,5);
}
可以像函数调用一样使用lambda表达式,和普通函数一样。不过我觉得还是直接用普通函数的好
详细介绍
c++11中的lambda表达式规范如下
其中
(1)是完整的lambda表达式
(2)const类型的lambda表达式
(3)省略了返回值类型的lambda表达式,返回类型可以根据函数体中的return语句推演,如果没有return则是void类型的lambda
(4)省略了参数列表,表示没有参数,类似f()
mutable修饰符号说明lambda表达式体内的代码可以修改被捕获的变量,这部分可以省略。按值传递函数对象参数时,加上mutable修饰符后,可以修改按值传递进来的拷贝(注意是能修改拷贝,而不是值本身)。
exception说明lambda表达式可否抛出异常,以及什么异常,类似void fun() throw(x,y)
attribute来声明属性
parameter指定参数
另外,catpure指定了在可见域范围内的lambda表达式码内可见的外部变量的列表
---[a,&b]a变量传值,b变量传引用
---[this]以值的方式捕获this指针,函数体内可以使用lambda所在类中的成员变量
---[=]函数体内可以使用任何lambda所在范围的的局部变量包括this指针,并且是传值的
---[&]函数体内可以使用任何lambda所在范围内的局部变量包括this指针,并且是传引用
---[=,&a,&b]除ab之外,其他都是传值
---[]不捕获外部的任何变量
综合实例
vector<int> v;
v.push_back(1);
v.push_back(2);
//无参数输出
{
for_each(v.begin(),v.end(),[](int t){cout<<t<<endl;});
}
//局部变量传值
{
int a=10;
for_each(v.begin(),v.end(),[=](int t){cout<<t+a;});
}
//传引用,修改变量。输出11,13,12
{
int a=10;
for_each(v.begin(),v.end(),[&])(int t)mutable{cout<<t+a;a++});
cout<<a;
}
//传值,修改变量。输出11,13,10
{
int a=10;
for_each(v.begin(),v.end(),[=])(int t)mutable{cout<<t+a;a++});
cout<<a;
}
//传引用修改变量,可以不用mutable.输出11,13,10
{
int a=10;
for_each(v.begin(),v.end(),[&a])(int t){cout<<t+a;a++});
cout<<a;
}
//空表达式
[](){}();
[]{}();
auto a=10;
auto b='a';
auto s(“hello”);
auto it=vecto.begin();
auto func = [](){cout<<”hello”<<endl;};
//返回值占位
template<typename T1,typename T2>
auto compose(T1 t1,T2 t2) → decltype(t1+t2){
return t1+t2;
}
auto v = compose(1,1.2);//v是double类型
注意事项
可以使用volatile、const、&、*、&&来修饰auto
const auto a=5;
auto *pk = new int;
必须初始化,不能auto a;
auto不能和其他类型一起使用,比如int
模板不能声明成auto,比如template是错的