指针的类型(即指针本身的类型) 和指针所指向的类型是两个概念
1.函数既有定义又有声明时,声明时指定后,定义就不能再指定默认值。
2.gdb调试
g++(或gcc) -g .cpp(或.c)使用gdb调试
gdb a.out
-l(后面没接行数的话,默认重中间开始,跟行数的话,把该行数放到中间显示) 查看函数代码
-b(break) 行号(设置断点)
-info b (查看断点信息)
-p 变量名(查看变量的值)
-n(next) 执行下一个语句
-c(continue) 程序运行到下一个断点
-bt(backtrace) 查看栈区
-r 程序运行到断点
3.宏和函数的效率及内联函数的使用
对一个系统来说频繁的压栈弹堆栈会占用系统很多的资源(如递归)调一个函数的时候需要压栈(保存现场:保存断点;保存相关状态信息,主要是状态寄存器的值;还有一些变量的值,主要使系统变量),但使用函数可以模块化,可阅读性高,看起来更加的直观。对宏来说没有弹压栈,是直接编译到可执行文件中,所以效率更高,但使用宏可能因为少括号的原因出错,可阅读性差。所以c++引入了内联函数,既解决了效率的问题又解决了可阅读性差的问题。它被调用时并不是想函数一样,压入到栈中,而是嵌入到可执行文件中,所以宏和内联函数弄成可执行文件可能会比较大。
4.带参数的宏和函数的区别
两者的定义形式不一样。宏定义中只给出形式参数,而不指明每一个形式参数的类型;而在函数定义中,必须指定每一个形式参数的类型。
函数调用是在程序运行时进行的,分配临时的内存单元;而宏替换则是在编译前进行的,并不分配内存单元,不进行值得传递处理。
函数调用时,要求实参和形参的类型一致;而宏调用时只用实参简单地替换形参。
使用宏次数多时,宏展开后源程序会变长,因为每一次宏展开都会使源程序增长;而函数调用不使源程序变长。
同样由于宏只做简单的文本替换,并不进行参数类型检查,在C++中建议采用inline函数来替换带参数的宏。
5.引用
引用时一定要初始化(开辟了内存),指明该引用变量是谁的别名
typedef struct node
{
int age;
char *p;
int &k;
char &c;
}node_t;
这样写是合法的,因为这只是定义了数据结构,还未使用它(引用),未开辟内存,所以不需要初始化k和c,只用使用它才要初始化,一个引用占四个字节,所以
sizeof(node_t) = 16;
6.makefile
*7.声明和定义为什么不在同一个文件
同一编译单元内部的重名符号在编译期就被阻止了,而不同编译单元之间的重名符号要到链接器才会被发现。
*8.全局变量在程序编译的时候就已经开辟了内存,main函数是在运行的时候才开辟的内存。
9.一个空类默认有三个函数:默认构造函数,默认拷贝函数,默认析构函数和赋值操作符。当用到这些函数或操作符时,编译器才会为我们产生所需要的函数或操作符。(构造函数有且仅能调用一次)
*10.什么情况会引发拷贝构造函数的调用呢?
A a;
1) A b = a; 或者 A b(a);
2)void func(A a);
3) A& func();
注意A func()并不会调用拷贝构造函数
<
#include <iostream>
using namespace std;
#include <stdio.h>
class CExp
{
public:
CExp();
void print();
int add(int a,int b);
public:
int i_v;
float f_v;
};
CExp::CExp()
{
this->i_v = 3;
i_v = 3;
f_v = 3.3;
cout << sizeof(*this) << endl;
}
void CExp::print()
{
printf("&iv = %p\n",&i_v);
int k;
k++;
++k;
}
int main()
{
CExp e1,e2;
// p_fun = e1.print;
printf("&e1.i_v = %0x,&e1.f_v = %0x\n",&e1.i_v,&e1.f_v);
// p_fun = e2.print;
printf("&e2.i_v = %0x,&e2.f_v = %0x\n",&e2.i_v,&e2.f_v);
printf("&e1 = %p,&e2 = %p\n",&e1,&e2);
e1.print();
e2.print();
return 0;
}
15.C++构造函数调用顺序
15.1)创建派生类的对象,基类的构造函数函数优先被调用(也优先于派生类里的成员类);
15.2)如果类里面有成员类,成员类的构造函数优先被调用;
15.3)基类构造函数如果有多个基类则构造函数的调用顺序是某类在类派生表中出现的顺序而不是它们在成员初始化表中的顺序;
15.4)成员类对象构造函数如果有多个成员类对象则构造函数的调用顺序是对象在类中被声明的顺序而不是它们出现在成员初始化表中的顺序;
15.5)派生类构造函数
作为一般规则派生类构造函数应该不能直接向一个基类数据成员赋值而是把值传递给适当的基类构造函数否则两个类的实现变成紧耦合的(tightly coupled)将更加难于正确地修改或扩展基类的实现。(基类设计者的责任是提供一组适当的基类构造函数)
16.static在函数的作用域
17.void func() const 和 const void func() 有什么区别
前者修饰该函数为 const(即函数里不能修改数据),后者修饰函数返回值为const。
*18.如果有两个类是全局变量,它们不在同一个文件中,那么哪个类的构造函数先被调用?
需要注意的是全局变量在编译的时候就已经开辟了内存了,所以哪一个文件先被编译,哪一个类的构造函数就先被调用。不同的编译器有不同的选择。
19.函数模板和模板函数
函数模板实例化之后就变成了模板函数。
类模板实例化之后就变成了模板类,再实例化就变成了对象。
(这两个看后面)
常量指针和指针常量(看前面)
常量指针:const int*p
指针常量:int *const p
20.标准库类型(实际就是学习STL 容器类)
string的使用。 —是一个模板类
(typedef basic_string string;)
vector的使用。 —是一个类模板
凡是容器类,扩容都是按指数形式增加。凡是容器类都有迭代器。
21.重载要求函数的参数不一样,而且要在同一个类里面。
22.派生类会继承基类的私有变量吗?
答案是肯定的。派生类会从基类没有选择的接受所有的成员(除构造和析构函数),这里所的基类的“私有成员”,所谓私有,不是说只有基类拥有!而是说,这些私有成员只能在基类内直接使用,不能在派生类或者类外直接使用,所以说私有,不是独自拥有,而是说基类私有直接使用权。
#include <iostream>
using namespace std;
#include <stdio.h>
class A
{
public:
A(int a)
{
this->a = a;
}
int get_i_value()
{
return a;
}
private:
int a;
};
class B : public A
{
public:
B(int a);
};
B::B(int a) : A(a)
{
}
int main()
{
B b(4);
int i = 0;
printf("sizeof(B) = %d\n", sizeof(b)); //通过测量b的大小可以得知A的私有变量是有被继承的
i = b.get_i_value(); //通过A的方法来获取A的私有变量
printf("i = %d\n", i);
return 0;
}
22.初始化列表中,只有继承才可以直接使用类名。
23.继承和包含的关系
构造函数顺序。 包含和继承 那个先
顺序:基类的构造函数->包含的构造函数->自己的构造函数(从内存的关系去理解或者打印它们的地址)
#include <iostream>
using namespace std;
#include <stdio.h>
class A
{
public:
A(int a)
{
cout << __func__ << " " << __LINE__ << endl;
this->a = a;
printf("A = %p\n", this);
}
int get_i_value()
{
return a;
}
private:
int a;
};
class B
{
public:
B()
{
cout << __func__ << " " << __LINE__ << endl;
}
};
class C : public A
{
public:
C(int a);
B b;
};
C::C(int a) : A(a)
{
cout << __func__ << " " << __LINE__ << endl;
}
int main()
{
C c(10);
printf("C = %p\n", &c);
printf("B = %p\n", &c.b);
return 0;
}
*24.类的指针(大类型数据和小类型数据的转换关系)
大类型的指针指向小类型的数据,不安全,可能操作非法内存
一般来说的都是 小类型的数据 操作 大类型的数据。(指针所指的范围是类型的范围!)
1)指针互指
2)对象互相赋值
3)互相引用
#include <iostream>
using namespace std;
class base
{
public:
base()
{
base_v = 1;
}
void base_func()
{
cout << "------------------->base fun"<< endl;
}
public:
int base_v;
};
class derived : public base
{
public:
derived()
{
derived_v = 2;
}
void derived_func()
{
cout << "------------------->derived fun" << endl;
}
public:
int derived_v;
};
int main()
{
base b_v;
derived d_v;
base *b = &b_v;
derived *d = &d_v;
b->base_func();
d->derived_func();
// 1.指针互指
b = (base *)&d_v;
d = (derived *)&b_v;
b->base_func();
d->base_func();
cout << d->derived_v << endl; // 访问非法内存,该值的结果未定义
// 2.对象互相赋值基类和派生类互相转换
base b_v1 = d_v;
b_v1.base_func();
// 通过拷贝构造区理解
// derived d_v1 = (derived)b_v;
// 3.引用
base &b_v2 = b_v;
base &b_v3 = d_v;
b_v2.base_func();
b_v3.base_func();
return 0;
}
#include <iostream>
#include <string>
using namespace std;
class Student
{
public:
Student(int, string, float);
void display();
private:
int num;
string name;
float score;
};
Student::Student(int n, string nam, float s)
{
num = n;
name = nam;
score = s;
}
void Student::display()
{
cout << endl << "num :" << num << endl;
cout << "name :" << name << endl;
cout << "score :" << score << endl;
}
class Graduate : public Student
{
public:
Graduate(int, string, float, float);
void display();
private:
float pay;
};
Graduate::Graduate(int n, string nam, float s, float p) : Student(n, nam, s), pay(p)
{
}
void Graduate:: display()
{
Student::display();
cout << "pay :" << pay <<endl;
}
int main()
{
Student stu1(1001, "Li", 87.5);
Graduate grad1(2001, "Wang", 97.5, 1000.5);
Student *pt = &stu1;
pt->display(); // 调用的是基类的display
pt = &grad1;
grad1.display();
pt->display(); // 调用的是基类的display,因为它指向的内存大小是基类的内存的大小。
return 0;
}
#include <iostream>
using namespace std;
class base
{
public:
base() {
base_v = 1;
}
void base_fun() {
cout << "------------->base fun" << endl;
}
public:
int base_v;
};
class derived:public base
{
public:
derived() {
derived_v = 2;
}
void derived_fun() {
cout << "------------------->derived fun" << endl;
cout << derived_v << endl;
}
public:
int derived_v;
};
int main()
{
derived *d_p = NULL;
base b;
d_p = (derived *)&b;
d_p->base_fun();
d_p->derived_fun(); //为什么可以调用派生类的函数,主要原因是函数在编译的时候已经确定内存地址了,所以可以调用它。
// cout << d_p->derived_v << endl;
}
对象互相赋值。(基类的值赋给派生类为什么不行,而派生类赋给基类却可以?)
派生类对象可以赋值给基类对象是因为,在内存的结构里开始是基类的数据,后来才是自己的数据。(《inside the c++object model》)
派生类对象也“是”基类对象,但两者不同。派生类对象可以当做基类对象,这是因为派生类包含基类的所有成员。但是基类对象无法当做成派生类对象,因为派生类可能具有只有派生类才有的成员。(也可通过拷贝构造函数来理解)
*25.多态(可理解为函数指针的变种):相同的调用,不同的响应。
c语言可通过函数指针实现多态。函数指针类似于多态,程序编译时函数的地址已经确定,但是函数指针的地址还没有确定,它是在运行时才确定的。可以通过反汇编来验证。
objdump -D a.out > a.out.s(linux系统下)
#include <iostream>
using namespace std;
typedef void (*func)();
void print_hzq()
{
cout << "i name is hzq" << endl;
}
void print_hxd()
{
cout << "i name is hxd" << endl;
}
void print_hjh()
{
cout << "i name is hjh" << endl;
}
int main()
{
char i;
func pfunc;
while(1)
{
cout << "Please input your choose:";
cin >> i;
switch(i)
{
case '1':
{
pfunc = print_hzq;
}
break;
case '2':
{
pfunc = print_hxd;
}
break;
case '3':
{
pfunc = print_hjh;
}
break;
default:
break;
}
(*pfunc)();
}
return 0;
}
26.类的指针
是否安全,查看操作的内存是否开辟。
注意:1)指针所指的内存区的大小。
2)找调用的函数就从它所指的内存区去找
27.类成员的访问权限
私有成员(private):可以被类自身的成员和友元访问,但不能被包括派生类在内的其他任何类和任何普通函数访问
公有成员(public):可以被任何普通函数和任何类的成员函数访问
保护成员(protected):可以被类自身的成员和友元访问外,还可以被派生类的成员函数访问,但不能被任何非友元的普通函数访问