###C/C++笔记

点击查看Evernote原文

#@author:       gr
#@date:         2014-07-20
#@email:        forgerui@gmail.com

c/c++笔记。
Life is too short to learn C++.

目录

一、malloc与calloc区别

 1. malloc不初始化分配的内存,已分配的内存中可以是任意的值. 
    calloc 初始化已分配的内存为0

 2. malloc返回的是一个对象
    calloc返回的是一个数组

二、static作用

  1. 限制了变量的作用域
  2. 改变了变量的存储域

三、内存释放

 1. new --> delete
 2. malloc --> free

四、栈和堆

 1. 栈是由编译器自动分配释放的,堆是程序员自己申请释放
 2. 栈有默认大小(1M),是连续的空间,比堆要空间小,堆不连续,空间大
 3. 申请 栈: int a;比较快 堆: malloc(1024); new() 比较慢

五、size_t

 1. size_t是标准C库中定义的,应为unsigned int,在64位系统中为 long unsigned int。 
 2. 在C++中,设计 size_t 就是为了适应多个平台的 。size_t的引入增强了程序在不同平台上的可移植性。

六、sizeof

 1. char *p; sizeof(p); sizeof(*p);  //p指针的大小为4,*p的大小是char型,为1

七、explicit

 1. C++中的explicit往往用来修饰构造函数包括拷贝构造函数,一般构造函数有两种功能,1.是构造器,2.是隐藏且默认的类型转换操作符。为使构造函数只做为构造器,声明为explicit,则必须显式调用构造函数才行,即A a(),不能使用A a = 1。

八、纯虚函数

 1. 如果函数中有纯虚函数就是抽象类,需要子类进行重写才能进行实例化,而只有virtual成员函数的话,可以实例化。

九、C++中的静态绑定和动态绑定

 1. 静态绑定:在声明时采用的类型,发生在编译期。
 2. 动态绑定:目标所指的类型;在运行期决定。 
 3. 静态类型在声明时就确定了,无法修改;动态绑定可以在程序中修改,指向其它类型。
 4. no-virtual函数是静态绑定的,即非虚函数根据声明调用;virtual函数是动态绑定的,即虚函数是根据定义决定的。
 5. 只有虚函数才使用的是动态绑定,其他的全部是静态绑定。目前我还没有发现不适用这句话的,如果有错误,希望你可以指出来。
 6. 当缺省参数和虚函数一起出现的时候情况有点复杂。虚函数是动态绑定的,但是为了执行效率,缺省参数是静态绑定的。 

十、C++中的声明和定义

 1. 可以多次声明,但只能定义一次
 2. 在函数外部声明同时会定义,在函数内部声明不会定义。
 3. 在函数外部可以使用extern int a;表示只声明不定义。但extern int a = 1;这样也会定义。

十一、const问题

有下面的一段代码:

class Rational{
 
private:
	int numer, deno;
public:
	Rational(int n = 0, int d = 1);
	int num()const {return numer;}
	int den() {return deno;}
};
 
Rational operator* (const Rational& l, const Rational& r){
	return Rational(l.num() * r.num(), l.den() * r.den());
}

l.num()r.num()是没问题的,而l.den()r.den()是存在问题的。因为num()是个const,而den不是const,当把对象当作pass-by-reference-to-const时,它里面的操作必须保证不得改变成员的值,所以,这个函数中调用对象的成员函数也必须是const的,所以要把den()函数改为const函数。

const在C语言中是表示道义上保证变量的值不会被修改,并不能实际阻止修改,通过指针可以修改常变量的值,但是会出现一些不可知的结果。几种情况不同,我们一个一个来看。

1、直接赋值

const int a = 3;
a = 5;
// const.c:6:2: error: assignment of read-only variable ‘a’
这种情况不用多说,编译错。

2、使用指针赋值,@孙健波提到的方法,在gcc中的warning,g++中出现error,是因为代码写得不对,由非const的变成const不用显式的转换,const变为非const需要显式转换,这种情况应当使用显式的类型转换。

const int a = 3;
int* b = (int*) &a;

printf("a = %d, *b = %d
", a, *b);
*b = 5;
printf("a = %d, *b = %d
", a, *b);

运行结果(注:使用msvc编译的结果一致):

$ gcc const.c
$ ./a.out
a = 3, *b = 3
a = 5, *b = 5

$ g++ const.cpp
$ ./a.out
a = 3, *b = 3
a = 3, *b = 5

这里使用g++编译时,a的值之所以没有改变,是因为编译时a是常量,然后被编译器编译为立即数了。因此对使用b指针修改不会更改a的值。

值得注意的是,如果a被定义为全局常变量,使用指针修改会引发segment fault。

在函数的原型中,我们也常用const修饰指针,表示函数的实现者在道义上不会去修改这个指针所指向的空间。例如我们熟知的strcpy函数,原型如下:

char* strcpy(char* dst, const char* src);

传入的参数src类型是const char*,表示函数内部实现不会修改src所指向的空间。之所以说是道义上,是因为在内部通过上述指针强制类型转换的方式可以修改该空间的值。另外,如果我们声明了我们不会修改传入指针的所指向的空间,那么我们也不应当去修改这块空间,因为这个传入的指针可能会是一个不可写的内存,然后出现段错误。

十二、构造函数的默认参数

当类的构造函数有默认参数时,便可以匹配<=参数个数的构造函数,没有给出的参数使用默认值初始化。

十三、当以pass-by-value传递参数时,需要考虑拷贝构造函数是否正确。

十四、类体中的函数自动内联

类体中定义的成员函数,如果简单,编译器会自动变为内联函数。

十五、头文件(详见 Topic 二十二)

在头文件可以声明变量,不可以定义变量,即必须加上extern,否则一旦这个头文件被两个源文件#include,便会报重定义的错误(因为#include后便会在多处定义)。如果实在想定义一个全局变量,应该在cpp中定义,这样才不会报错。头文件的作用是提供声明,方便别人进行编译,切不可在头文件中进行定义。
但可以在头文件中定义下面三个东西:类,const变量(只初始化一次),内联函数, 模板函数。

十六、头文件与重复定义

头文件卫士不能解决变量重定义的问题,它的目的是要保护嵌套的包含指令中的内部链接属性的名称不被重复定义。它避免了在一个程序文件中重复include两次,但在另一个程序文件中还是可以include这个头文件,所以如果在头文件定义变量,即使使用了头文件卫士,还是会有重定义的问题。

十七、OpenCV的Debug与Release

一直读入图片出错,把Debug修改成Release版本就解决了,估计是OpenCV中的dll调试与运行版本问题。

十八、二维数组delete

二维数组的delete要进行两次删除。

// Definition
double** m_matrix;
// Release
if ( m_matrix != NULL ) {
    // free arrays
    for ( unsigned int i = 0 ; i < m_rows ; i++ ) {
        delete [] m_matrix[i];
    }
    delete [] m_matrix;
}
m_matrix = NULL:

十九、 matrix(x, y)的实现

定义一个Matrix类,初始化对象Matrix matrix;实现下面的两个操作。

//两种功能, 一个是赋值,一个是取值
matrix(x, y) = a;
cout << matrix(x,y);

实现需要使用operator ()操作符。

//赋值,返回的是引用,可以修改其值
template <typename T>
inline T& operator () (int i, int j){
    return matrix[i][j];
}
//取值,返回的是const
template <typename T>
inline const T& operator() (int i, int j) const{
    return matrix[i][j];
}

二十、 list vs vector

vector 可以使用下标访问,list 不能用下标的形式访问。
vector遍历删除:

vector<Track>::iterator it1 = trajs.begin();
while (it1 != trajs.end()){
    if (it1->isLost()){
        trajs.erase(it1);
    }else{
        it1++;
    }
}

list 遍历删除:

vector<Track>::iterator it1 = trajs.begin();
while (it1 != trajs.end()){
    if (it1->isLost()){
        // For the vector erase the element, so it1--
        cout<<"delete: "<<it1->getId()<<endl;
        it1 = trajs.erase(it1);
    }else{
        it1++;
    }
}

二十一、sprintf,sscanf

sprintf:把变量打印到字符串中,从而获得数字的字符形式(可以实现将整形转换成字符型)

int value = 10;
char str[128];
// 结果为: "the result of value is 10",将数值转换为字符
sprintf(str, "the result of value is %d", value);

sscanf: 从一个字符串中读进与指定格式相符的数据. 格式可以是整型数据等。

//str="ab",取到非a-z为止
sscanf("ab123D", "%[a-z]", str);
//str=空,取到a-z为止,因为第一个就是a,所以不取
sscanf("ab123D", "%[^a-z]", str);
//str="DE12",取到a-z为止
sscanf("DE12ab123D", "%[^a-z]", str);

二十二、头文件

概括: 将声明放在头文件中,实现放在cpp中。

  • 全局变量,全局函数声明放在头文件中,实现放在cpp中。
  • 将类、结构体的声明,定义放在头文件中。
  • 类成员函数声明放在头文件中,实现放在cpp中。
  • inline相关的函数、template相关的函数和类 实现放在头文件中。
  • static const修饰的变量的初始化放在头文件中,类中static变量的初始化放在cpp中, const的初始化放在初始化列表中。

二十三、比较与赋值耗时

比较耗时长
比较有一个读a和b值得过程,然后比较要建立一个新的区间存储比较结果,
而复制只是读取b的值,然后存储到a的位置,根本不需要读取a原来的值。

二十四、奇怪的undefined reference

typedef struct  {
    int nFeatures;
    KLT_Feature *feature;
    void WriteToImage(const std::string imgName, const cv::Mat &img) const;
}  KLT_FeatureListRec, *KLT_FeatureList;

定义的结构体有KLT_FeatureListRec*KLT_FeatureList,但其中成员函数WriteToImage实现在cpp文件中,如下使用会造成undefined reference

KLT_FeatureList fl;
fl->WriteToImage(std::string(filename),img);

但把WriteToImage的实现放在struct里,这时便可以正常编译。应该是KLT_FeatureList无法匹配到KLT_FeatureListRec的成员函数。

二十五、free,delete

free之后变为野指针,要置为NULL

二十六、默认析构函数

默认析构函数不会释放类型为内置指针的成员变量指向的空间。

二十七、默认拷贝构造函数

  1. 默认拷贝构造函数对指针类型进行地址复制,即多个指针指向同一个地址,如果需要拷贝到新的空间,需要自己实现拷贝构造函数。

  2. 拷贝构造时,复制“对象内的所有成员变量” 及 “所有base成分”,可以通过Child::operator=(const Parent& rhs)去调用子类拷贝构造函数,复制base成分

     class Children{
     private:
         int x;
     public:
         Children& operator=(const Children& rhs){
             x = rhs.x;
             return *this;
         }
     };
     class Parent : public Children{
     private:
         int y;
     public:
         Parent& operator=(const Parent& p){
             y = rhs.y;
             //一定要!!!调用子类operator=,拷贝base成分
             Children::operator=(p);
             return *this;
         }
     };
    

二十八、类成员变量的初始化

  1. 普通变量可以在初始化列表初始或在构造函数中赋值
  2. const变量只能在初始化列表中初始化。
  3. static变量只能在类外初始化,且不能在函数中定义,必须在全局定义。如果不初始化,直接使用就只有声明没有定义,会报错,vs中会报无法解析的外部符号。
  4. const static静态常量可以直接在类的定义中初始化
  5. Reference类型在初始化列表中初始化。

有三种包括引用const基类要通过初始化列表初始化。

注意:
C++11中可以为数据成员提供一个类内初始值,即可以在类中直接初始化数据成员,没有初始值的成员将被默认初始化。

二十九、拷贝构造函数

拷贝构造函数第一个参数是自身类类型(且必须是引用类型,传值会导致死循环),后面的参数都应该提供默认值。

三十、拷贝构造、拷贝赋值

拷贝构造函数和赋值操作符之间不能相互转换,如果调用的函数没有实现,则会调用编译器默认生成的函数。

A a = b;    //调用的是拷贝构造,等同于A a(b);
A a;
a = b;      //调用的是拷贝赋值

三十一、 cout char*

如果想要输出指针地址,使用static_cast<const void*>转型。

char* str = "Hello world";
cout << str << endl;        //输出的是字符串,不是地址
cout << static_cast<const void *>str << endl;           //输出字符指针的地址

三十二、 在类的成员函数中可以访问同类型实例的私有变量

类成员变量的访问权限是编译器强加的,只要编译器可以找到符号,就可以通过编译。

在类中的同类实例的私有变量符号可以在当前类域中查找到,所以可以通过编译;但在类外,因为无法访问类的私有变量,无法找到符号。

三十三、 初始化二维数组

1.A (*ga)[n] = new A[m][n];
...
delete []ga;
缺点:n必须是已知
优点:调用直观,连续储存,程序简洁(经过测试,析构函数能正确调用)

2. A** ga = new A*[m];
for(int i = 0; i < m; i++)
ga[i] = new A[n];
...
for(int i = 0; i < m; i++)
delete []ga[i];
delete []ga;
缺点:非连续储存,程序烦琐,ga为A**类型
优点:调用直观,n可以不是已知

3. A* ga = new A[m*n];
...
delete []ga;
缺点:调用不够直观
优点:连续储存,n可以不是已知

4. vector<vector<A> > ga;
ga.resize(m); //这三行可用可不用
for(int i = 1; i < n; i++) //
ga[i].resize(n); //
...

缺点:非连续储存,调试不够方便,编译速度下降,程序膨胀(实际速度差别不大)
优点:调用直观,自动析构与释放内存,可以调用stl相关函数,动态增长

5. vector<vector<A> > ga;
ga.resize(m*n);
方法3,4的结合

6. 2的改进版
A** ga = new A*[m];
ga[0] = new A[m*n];
for(int i = 1; i < m; i++)
ga[i] = ga[i-1]+n;
优点:连续存储,n可以不是已知,析构方便,猜想只需delete [] ga;

三十四、 格式化输出

#include <iomanip>
cout << setfill('*') << setw(6) << count << endl;

三十五、 随机数

C中的随机数:

#include <stdlib.h>
a = rand() % 100;       //生成0到100的随机数
a = srand((unsigned) time(NULL));	//使用系统当前时间作为种子点

C++中的随机数:

//随机数引擎
default_random_engine e;
for (size_t i = 0; i < 10; ++i)
	cout << e() << " ";

标准库定义了多个随机数引擎类,区别在于性能和随机性质量不同。引擎的操作如下:

Engine e;
Engine e(s);		//使用整型值s作为种子点
e.seed(s);			//重新设置种子为s
e.min()	e.max()		//引擎生成的最小值和最大值

为了得到一个指定范围内的数,我们使用一个分布类型的对象:

uniform_int_distribution<unsigned> u(0, 9);
default_random_engine e;
for (size_t i = 0; i < 10; ++i)
	cout << u(e) << " ";		//生成均匀分布的unsigned值

36、内存操作

#include <string.h>
memcpy(dst, src, size);
memset(src, 0, size);
int* a = (int*)malloc(size);
if (a)
    free(a);

37. c string

#include <string.h>
char* str = "hello world";
int len = strlen(str);
strcmp(str1, str2);
strcpy(str1, str2);
strncpy(str1, str2, size); //拷贝前size个字符
strcat(str1, str2);
strncat(str1, str2, size);   //连接前size个字符
char *strchr(const char *s,char c);         //查找字符串s中首次出现字符c的位置
strstr(string,search);              //查找字符串s中出现字符串search的位置

38. clock vs time

clock是cpu用时,time是从UTC时间开始计时,程序真正执行的时间。

// first
#include <time.h>
clock_t startTime = clock();
int elapsedTime = (clock() - startTime) / CLOCKS_PER_SEC;

// second
time_t a, b;
a = time(NULL);
//...
b = time(NULL);
float elapsed = b - a;

39. shared_ptr

gcc中的头文件

//vs2010使用#include <memory>, 
//c++11中也直接#include<memory>,已将将它加入标准空间std中了,不需要使用tr1了

#include <tr1/memory>       


using std::tr1::shared_ptr;

void foo(){
    // c++11嵌套<>已经可以区分,不用加空格,但为了保证兼容性,最好还是加上空格,因为c++11目前使用的还比较少
    shared_ptr<Matrix<int> > ptrm(new Matrix<int>(3, 4));
    ptrm->print();
}

shared_ptr<Widget> s1(new Widget());
cout << s1.use_count() << endl;    //当前有1个reference
shared_ptr<Widget> s2 = s1;
cout << s1.use_count() << endl;     //当前有2个reference
s1->print();                         //调用Widget类内函数
s1.reset();                         
cout << s2.use_cout() << endl;      //当前有1个reference
s2.reset();                         //会调用Widget的析构函数

40. C++类型转换

  1. const_cast:常量转非常量
  2. static_cast: 最常用,将intdouble
  3. dynamic_cast:安全向下转型,转换为子类,效率低
  4. interpret_cast:转换一个指针为其它类型的指针。它也允许从一个指针转换为整数类型。反之亦然。(译注:是指针具体的地址值作为整数值?)这个操作符能够在非相关的类型之间转换。操作结果只是简单的从一个指针到别的指针的值的二进制拷贝。在类型之间指向的内容不做任何类型的检查和转换。

reinterpret_cast:

class A {};
class B {};

A * a = new A;
B * b = reinterpret_cast<B *>(a);

reinterpret_cast就像传统的类型转换一样对待所有指针的类型转换。

dynamic_cast:

class A
{
    public:
        int m_iNum;
        virtual void f(){}
};

class B:public A
{
};

class D:public A
{
};
void foo()
{
    B* pb = new B;
    pb->m_iNum = 100;
    D* pd1 = static_cast<D*>(pb); //compile error
    D* pd2 = dynamic_cast<D*>(pb); //pd2 is NULL
    delete pb;
}

在函数foo中,使用static_cast进行转换是不被允许的,将在编译时出错,而使用 dynamic_cast的转换则是允许的,结果是空指针。dynamic_cast只有当pb真的指向class D的实例时才会有效。

41. 数值

2^16 = 65536
2^20 = 1048576

42. const

  1. const 作用域

     const char * p = "hello";       //p所指向的字符串不能变
     char const * p = "hello";       //同上效果
     char* const p = "hello";        //p的值无法改变,不能指向它处
    
  2. const 重载

(1) const 修饰参数
+ 若参数是基本类型,不可构成重载
+ 若参数是自定义类型,可以构成重载

(2) const 修饰函数
可以构成重载,const成员函数也不能在函数中调用其他非const 的函数

(3) const 修饰返回值
不可以进行重载

  1. const的例外

    const函数执行bitwise检查,函数里不可以改变值。若要进行改变,需要将变量声明为mutable,添加例外。

  2. const的调用

     对象.成员函数
          对象          成员函数         对/错
     1、  const         const            对
     2、  const         non-const        错 
     3、  non-const     const            对
     4、  non-const     non-const        对
     
     成员函数调用成员函数
          成员函数       成员函数         对/错
     5、  const         const            对
     6、  const         non-const        错
     7、  non-const     const            对
     8、  non-const     non-const        对
    

43. 线程安全

mutex:互斥
实现:

44. volatile

它是被设计用来修饰被不同线程访问和修改的变量。如果没有volatile,基本上会导致这样的结果:要么无法编写多线程程序,要么编译器失去大量优化的机会。volatile修饰的变量可能随时发生变化,每次都要重新读取,编译器不要进行优化。

volatile的作用是: 作为指令关键字,确保本条指令不会因编译器的优化而省略,且要求每次直接读值.

45. binary_function

template <typename T>
struct com : public binary_function<Matrix<T>, Matrix<T>, bool>{
    bool operator() (const Matrix<T>& lhs, const Matrix<T>& rhs) const{
       return lhs.getD() < rhs.getD(); 
    }
};

//排序算法中调用
sort(v.begin(), v.end(), com<int>());
//或者先定义一个对象,再传进去
com<int> in;
sort(v.begin(), v.end(), in);

46. 常量区

char str1[] = "abc";
char str2[] = "abc";
const char3[] = "abc";
const char4[] = "abc";
char *str5 = "abc";
char *str6 = "abc";
const char *str7 = "abc";
const char *str8 = "abc";

其中,str1, str2, str3, str4分别指向不同的地址,后面的四个指向共同的常量区。并且常量字符无法修改,尝试修改会出现Segment Fault

47. 进制

0x数据 十六进制
0数据 八进制
直接数据 十进制

而%d,%o,%x,%X 分别是 十进制,八进制,十六进制(小写),十六进制(大些)的输出格式!

48. extern "C"

C++语言支持函数重载,C语言不支持函数重载。函数被C++编译后在库中的名字与C语言的不同。假设某个函数的原型为: void foo(int x, int y);
该函数被C编译器编译后在库中的名字为_foo,而C++编译器则会产生像_foo_int_int之类的名字。
C++提供了C连接交换指定符号extern“C”来解决名字匹配问题。

49. strcpy

(1)strcpy实现

char *strcpy(char *strDest, const char *strSrc);
{
    assert((strDest!=NULL) && (strSrc !=NULL));	// 2分
    char *address = strDest;	// 2分
    while( (*strDest++ = * strSrc++) != ‘’ )	// 2分
       NULL ; 
    return address ;	// 2分
}

(2)strcpy能把strSrc的内容复制到strDest,为什么还要char * 类型的返回值?
答:为了实现链式表达式。 // 2分

int length = strlen( strcpy( strDest, “hello world”) );

50. 复杂声明

右左法则:首先从最里面的圆括号看起,然后往右看,再往左看。每当遇到圆括号时,就应该掉转阅读方向。一旦解析完圆括号里面所有的东西,就跳出圆括号。重复这个过程直到整个声明解析完毕。

  1. 先从未定义的标识符func开始,由于左边是*,说明func是个指针,跳出括号,看右边是个括号,func是个函数指针,指向的函数有int*的参数,返回值为int.

     int (*func)(int *p);
    
  2. func开始,左侧是*,说明是指针,跳出括号,右侧是个括号,说明func是个函数指针,其函数是两个参数,一个是int*,另一个是以int*为参数,int为返回值的函数指针,外面函数返回值类型是int.

     int (*func)(int *p, int (*f)(int*));
    
  3. 先看func,[]的优先级大于,先看左侧[],func是一个数组,再看*,这个数组存放的是指针,跳出括号,看右侧是个括号,可知数组中存放的是函数指针,其函数有一个int的参数,返回值为int.

     int (*func[5])(int *p);
    
  4. 先看func,其左侧是个*,可知是个指针,跳出括号,左侧是个数组,说明func是个指向数组的指针,右侧有个 *,说明func中存放的是函数指针,其函数有个参数是int*,返回值是int。

     int (*(*func)[5])(int *p);
    
  5. func是个函数指针,参数是int*,返回值是指向数组的指针,指向的数组有5个int

     int (*(*func)(int *p))[5];
    
  6. 也有一些不符合语法规则:不返回数组;不允许函数数组,因为一般函数大小不一样。

     int func(void) [5];
    
     int func[5](void);
    
  7. 其它

     int (*(*func)[5][6])[7][8];
    
     int (*(*(*func)(int *))[5])(int *);
    
     int (*(*func[7][8][9])(int*))[5];
    
  8. 对表达式分解会更加清晰

     int (*(*func)(int *p))[5];
    

    可以这样分解:

     typedef int (*PARA)[5]; 
     typedef PARA (*func)(int *);
    

51. typename vs class

  1. typenameclass都可以用来在模板中声明模板参数和类型参数。早期只有class,后来为了区分类,提供了typename,在这一方面,typenameclass一样,根据习惯使用。

  2. typename还有一个用途,标明内嵌类型名,这时只能使用typename

     typename vecotr::const_iterator start(v.begin());			//声明内嵌类型const_iterator
    

52. C++ 三个基本特征

封装:隐藏实现细节,使代码模块化。把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行隐藏。(代码重用)

继承:继承可以使用现有类的所有功能,无需重新编写原来的类而对其进行扩展。(代码重用)

多态:父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作,允许将子类类型的指针赋值给父类类型的指针。

53. 继承方式

  1. 继承方式

    public继承: public => public, protect =>protect, private => 不可访问
    protect继承:public => protect, protect =>protect, private => 不可访问
    private继承:public => private, protect => private, private => 不可访问

  2. 子类成员

    public成员:public => public, protect => protect, private => private
    protect成员:public => protect, protect => protect, private => private
    private成员:都不可访问

默认继承方式是private继承,默认成员是private。

54. ,表达式

逗号表达式的一般形式是:表达式1,表达式2,表达式3……表达式n
逗号表达式的求解过程是:先计算表达式1的值,再计算表达式2的值,……一直计算到表达式n的值。最后整个逗号表达式的值是表达式n的值。

int x = 7, y = 2, z;
z = (x%y,x/y)  先计算x%y =1, 再计算x/y=3,然后 z=(1,3)=3

55. 栈展开

因发生异常而逐步退出复合语句和函数定义的过程,被称为栈展开。

栈展开过程沿着嵌套函数的调用链不断查找,直到找到与异常匹配的catch子句为止,如果没有找到匹配的子句,调用标准库中的terminate函数终止进程

在栈展开过程,在一些语句块创建的对象应该被正确销毁,调用其析构函数。

56. 异常

  1. catch子句的参数类型

    使用pass by reference方法,可以对异常的修改起作用并且提高效率,一般将catch的参数类型定义为引用类型。

  2. 查找匹配的catch子句

    a. 类型转换支持的比函数参数匹配的少,不支持算术类型转换和类类型转换,只支持如下:

    • 非常量向常量转型
    • 派生类向基类转型
    • 数组被转换成指向数组类型的指针

    b. 匹配顺序
    函数匹配是best-fit进行的,选择匹配参数最好的,而异常问题是按first fit进行,按照顺序进行,所以要把子类(及特殊类型)放在通用类型的前面。

  3. 异常抛出

    异常抛出:

     Exception ex;
     throw ex;
    

    异常抛出时,不论catch是以什么方式捕获by referenceby value都需要进行复制,到catch子句上的正是这个异常的副本。

    异常重新抛出:

     catch (exception& ex)
     {
     	//....
     	throw;					//重新抛出此exception,使它继续传播
     }	
     catch (exception& ex)
     {
     	//....
     	throw ex;				//传播被捕捉的exception的一个副本
     }
    

57. VS2010的配置选项

  1. 如果用到dll文件,可以添加一个后期生成事件,拷贝dll文件到Debug目录:

     copy D:appvlfeat-0.9.9inw32vl.dll $(SolutionDir)$(ConfigurationName)
    

58. 内联函数

  1. 在内联函数内不要使用循环语句和开关语句,这样就达不到内联的效果了。
  2. 关键字inline必须与函数定义体放在一起才能使函数成为内联,仅将inline 放在函数声明前面不起任何作用

59. memset

memset的用法如下:

const int NUM = 100;
int* src = new int[NUM];
memset(src, 0, sizeof(int)*NUM)

memset是按字节赋值的,不能将int赋1,如果赋1,实际结果将是0x01010101,与我们的预期不同。

60. virtual 析构函数

当子类对象经由父类的指针删除时,如果是non-virtual函数,将不会调用子类的析构函数,也就是说,对象的子类成分有可能没被销毁。

61. ifstream

  1. good() vs eof()

    good()表示文件流是否正常,eof表示文件流是否到结束了。good() 等价于 !(bad() || eof() || failed())

  2. ifstream in(string)

    有些编译器不支持以string作为参数,为了兼容性,最后还是转为c串。

62. strtok

char buf[]=”Golden Global View”;
char* token = strtok( buf, " ");
while( token != NULL )
{
    printf( ”%s “, token );
    token = strtok( NULL, ” “);
}

63. ::双冒号

//双冒号也常常用于在类变量内部作为当前域的元素进行表示,比如:    
int CA::add(int a)    
{    
	return a + ::ca_var;    
}
//表示当前类实例中的变量ca_var。
//如果当前哉没有,则向上一级域查找,直到找到全局域

64. 区别位运算与关系运算

位运算只有一个字符:&, |, ^
关系运算有两个字符:&&, ||

65. 静态分配,动态分配

内存的静态分配和动态分配的区别主要是两个:

  1. 一是时间不同。静态分配发生在程序编译和连接的时候。动态分配则发生在程序调入和执行的时候。
  2. 二是空间不同。堆都是动态分配的,没有静态分配的堆。栈有2种分配方式:静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配。动态分配由函数malloc进行分配。不过栈的动态分配和堆不同,他的动态分配是由编译器进行释放,无需我们手工实现。

堆和栈都可以动态分配,但堆必须动态分配,不能静态分配。

66. 笔试题

  1. 字符串大小

     char dog[] = "wangmiao";
     sizeof(dog)     //大小为10
     strlen(dog)     //大小为4
    
  2. 系统位数对类型大小的影响

    联合体,结构体的总大小为结构体最宽基本类型成员大小的整数倍。

     32位: int 4; 指针 4;
     64位: int 4; 指针 8;
    

    64位机器:

     /*******size of types on centos_x32*****************/
     size of char:1
     size of int:4
     size of long :4
     size of float :4
     size of long long:8
     size of double:8
     size of long double:12
     size of char * :4
     /*******size of types on centos_x64*****************/
     size of char:1
     size of int:4
     size of long :8
     size of float :4
     size of long long:8
     size of double:8
     size of long double:16
     size of char * :8
     
              32位        64位 
     char      1           1 
     int       4           大多数4,少数8 
     long      4           8 
     float     4           4 
     double    8           8 
     指针       4           8
    
  3. 字符串初始化方法

     char a[] = "ABC";          //大小为4,最后有
     char b[] = {"A", "B", "C"};  //大小为3, 字符数组
    
  4. 数组指针和指针数组

     int* a[10];        //指针数组
     int (*b)[10];      //数组指针,指向有10个char组成的数组的指针
     sizeof(a);         //sizeof(int*) * 10 = 4 * 10 = 40(32位机器)
     sizeof(b);         //sizeof(指针)=4 (32位机器)
    
  5. 位域

    C语言又提供了一种数据结构,称为“位域”或“位段”。所谓“位域”是把一个字节中的二进位划分为几个不同的区域, 并说明每个区域的位数。每个域有一个域名,允许在程序中按域名进行操作。 这样就可以把几个不同的对象用一个字节的二进制位域来表示。

     struct A{
     	int x : 3;
     	int y : 4;
     	int z : 5;     //占用int中的5位 
     };
     
     sizeof(A);    //大小为4字节,因为只占用了一个int
    
  6. 纯虚函数

    纯虚函数可以有函数体,但没有什么意义。

  7. 类型转换

     32位机上根据下面的代码,问哪些说法是正确的?()
     signed char a = 0xe0;
     unsigned int b = a;
     unsigned char c = a;
     
     A. a>0 && c>0 为真
     B. a == c 为真
     C. b 的十六进制表示是:0xffffffe0
     D. 上面都不对
    
    1. 同等位数的类型之间的赋值表达式不会改变其在内存之中的表现形式,因此通过 unsigned char c = a;语句,c的位存储形式还是0xe0
    2. B选项中的"==" 左右两边需要进行Integer Promotion,将其提升为int类型。
    3. 提升的方法:如果原始类型为unsigned ,进行零扩展;如果原始类型为signed,进行符号位扩展,注意是原来的类型,不是提升后的类型。
    4. 如果unsigned和int进行计算,则会将int转化成unsigned之后进行计算,但不会改变内存的表现形式。

    比如下面的代码,返回的结果为真,因为a和b进行转型时,不发动内存内容,而==比较都会转换为int,所以最后结果相同。

     signed int a = 0xe0000000
     unsigned int b = a;
     cout<< (b == a) <<endl;
    
  8. ASCII表

    Alt text

     0x20:  space
     0x30:  0
     0x41:  A
     0x61:  a
    
  9. 汇编

    AT&T风格的汇编代码。

     int main()
     {
         char data = 'a';
         char* p = &data;
         *p++;
         (*p)++;
         return 0;
     }
    
     //使用objdump -S main 输出mian部分的汇编代码
     
     0000000000400746 <main>:
       400746:	55                   	push   %rbp
       400747:	48 89 e5             	mov    %rsp,%rbp
       40074a:	48 83 ec 20          	sub    $0x20,%rsp
       40074e:	64 48 8b 04 25 28 00 	mov    %fs:0x28,%rax
       400755:	00 00 
       400757:	48 89 45 f8          	mov    %rax,-0x8(%rbp)
       40075b:	31 c0                	xor    %eax,%eax
       
     l1:    char data = 'a';    
       40075d:	c6 45 ef 61          	movb   $0x61,-0x11(%rbp)   //rbp[-0x11] = 0x61
       
     l2:    char* p = &data;
       400761:	48 8d 45 ef          	lea    -0x11(%rbp),%rax
       400765:	48 89 45 f0          	mov    %rax,-0x10(%rbp)
       
     l3:     *p++;
       400769:	48 83 45 f0 01       	addq   $0x1,-0x10(%rbp)
       
     l4:    (*p)++;
       40076e:	48 8b 45 f0          	mov    -0x10(%rbp),%rax
       400772:	0f b6 00             	movzbl (%rax),%eax
       400775:	83 c0 01             	add    $0x1,%eax
       400778:	89 c2                	mov    %eax,%edx
       40077a:	48 8b 45 f0          	mov    -0x10(%rbp),%rax
       
       40077e:	88 10                	mov    %dl,(%rax)
       400780:	b8 00 00 00 00       	mov    $0x0,%eax
       400785:	48 8b 4d f8          	mov    -0x8(%rbp),%rcx
       400789:	64 48 33 0c 25 28 00 	xor    %fs:0x28,%rcx
       400790:	00 00 
       400792:	74 05                	je     400799 <main+0x53>
       400794:	e8 a7 fe ff ff       	callq  400640 <__stack_chk_fail@plt>
       400799:	c9                   	leaveq 
       40079a:	c3                   	retq   
    
  10. 数组名

    数组名是常量,无法给值赋值。

    char str[100];
    str++;    //出错,str是常量无法修改
    
  11. 类方法是指类中被static修饰的方法,无this指针。

原文地址:https://www.cnblogs.com/gr-nick/p/4002146.html