一般我们编写的程序都会引用基本的库函数,因此在运行程序之前还要把库函用定义好的目标代码替换,这个过程称为链接。就是把自己写的源代码的目标文件与库函数的目标文件组合起来,生成最终的可执行文件。链接由链接器完成。除了ascii码值表示的文件,其余的都是二进制文件,比如hello中用到了printf函数,这是标准c库的函数,存在于一个名为printf.o的单独编译好的目标文件中,这个文件必须以某种方式合并到我们编译好的的目标文件中。链接器(ld)程序负责处理这种合并,结果得到hello文件,它是可执行目标文件,可以被加载到内存中,由系统执行。
转换完成之后就是执行了。在unix系统中,shell是一个命令行解释器,输出一个提示符,等待用户输入然后执行命令。如果输入的第一个单词不是一个内置的shell命令,shell将其解释为可执行文件,比如输入./hello,它将加载并运行这个文件。hello在屏幕上输出信息,然后终止。shell输出一个提示符,等待下一个输入的命令行。具体的过程为:初始时,shell执行它的指令,等待输入。用户输入字符创“./hello”后,shell将字符逐一读入寄存器,然后存放到存储器中,敲回车键后,shell知道用户结束命令输入。然后shell执行一系列的指令来加载可执行的hello文件,将hello目标文件的代码和数据从磁盘复制到主存,数据包含输出的字符串"HELLO,WORLD\n"。一旦目标文件hello中的代码和数据被加载到主存,处理器开始执行main的机器语言指令,将字符串从主存拷贝到寄存器,并输出到屏幕上。
由于涉及大量的主存,磁盘,寄存器通信,故产生了cache等缓冲提高速度的设备,减少通信阻塞。
为了减少用户的负担,操作系统对计算机硬件资源进行了抽象,产生了进程,线程,虚拟地址等概念。进程是程序的一次执行,是操作系统分配资源的单位,多个进程是可以并发执行的,并发执行实际上每个时刻执行的还是一个进程,只不过进程间切换的速度比较快,给人的感觉是并发执行。操作系统为每个进程保存执行的状态信息,称为上下文,包括pc和寄存器文件当前值,主存内容等等。切换进程时,发生上下文切换。一个进程中可以有多个线程执行单元,每个线程都运行在进程的上下文中,共享同样的代码和数据,由于网络服务器等应用对并行处理的需求越来越大,多线程模型也越来越重要。虚拟地址为每个进程提供了一个假象,即每个进程都在独占主存,每个进程看到的是一致的存储器,称为虚拟地址空间。虚拟地址空间是由大量的准确定义的区构成,linux从低地址到高地址依次为:程序代码和数据;堆;共享库;栈;内核虚拟存储器
类模板
函数模板
成员函数模板
成员类模板
模板参数模板
容器#
迭代器
分配器
重新绑定
rebind用法及其前后脉络
算法 #
仿函数
适配器
concept
//1.表示字符串里的特殊字符
char stra[]={"\"C++\""};
//2.路径表示
char path[]={"C:\\tu\\text.c"}
//相当于
char path[]={"C:/tu/text.c"}
int i;
while(i<100){
printf(" ");
i++;
}
int n;
do
{
printf("%d",n);
n--;
}while(n<10)
for(int i=1;i<=10;i++){
printf("%d",n);
}
编译和链接与执行是分开的,如果没有编译完成,那么不存在程序被装在用户存储区,全局变量也没有被装进.data区
&:带&函数形参,使用并接收返回值,而 & 叫做引用符,它是 C++ 的内容(目前多数 C 语言编译器也能使用),它可以引用主函数中 x 的地址,而不在调用的函数栈帧中开辟空间,这样就可以对主函数中的 x 进行修改。
效果类似于使用并接收返回值。
*与&:
//指针传递
void swap(int *a, int *b){
cout<<"形参指针a的地址 = "<< a <<endl;
cout<<"形参指针b的地址 = "<< b <<endl;
int tmp = *a;
*a = *b;
*b = tmp;
}
int main(){
int a = 5;
int b = 10;
cout<<"实参变量a的地址 = "<< &a <<endl;
cout<<"实参变量b的地址 = "<< &b <<endl;
cout<<"实参变量a的值 = "<< a <<endl;
cout<<"实参变量b的值 = "<< b <<endl;
//调用函数,指针传递方式
swap(&a, &b);
cout<<"实参变量a的值 = "<< a <<endl;
cout<<"实参变量b的值 = "<< b <<endl;
getchar();
return 0;
}
//若采用指针传递的方式,我们在函数定义和函数声明时使用 *来修饰形参,表示这个变量是指针类型;在进行函数调用时,使用 & 来修饰实参,表示是将该变量的地址作为参数传入函数。
地址小端存储
系统如何访问指针
封装的类型,相对于内置类型的定义,需要用大括号初始化;
//定义
struct Student
{
char id[10];
char name[10];
char sex[5];
int age;
};//以上为结构体设置,相当于声明一种类型,须带分号
//}Stud;与struct Student Stud;等价,若在函数外声明相当于声明了一个全局结构体变量
//使用
struct Student stud={"02434","wxinxi","man",21};
//stu只是一个变量,不是一个对象
//结构体只是属性的集合,默认为公有,而类定义的对象是一个属性与方法的集合;
stud.age;//访问
struct Student *stup=&stud;
(*stup).id;
sp->id;//指针访问成员运算符
1标准文件:标准输出文件stdin:显示器printf(),putchar()
标准输入文件stdout:键盘scanf(),getchar()
标准错误文件:显示器
缓冲区:
cpu从键盘读?从缓冲区读?(CPU直接访问键盘的速度太慢)
读缓冲区:CPU访问到getchar,终止对getchar的访问,转而去处理别的任务,当getchar将数据全部读到缓冲区时,CPU才会回来处理
char ch;
scanf("%c",ch);
ch=getchar();
while(getchar()!="\n");{
sum=sum+1;
}
printf("sum:%d\n",sum);
2.宏常量
继承(继承了属性和方法)中保护与私有区别:在子类中保护可以访问,私有不能访问
若父类没有默认或缺省构造函数,必须在子类中显式调用父类构造进行初始化
多继承和多层继承
多态四种:(覆盖,基类的指针或引用)
重载多态(函数,运算符)
包含多态(虚函数 virtual)
强制多态(强制类型转换 _cast)
参数多态(模板,传过去参数不同,运用函数不同)
动态联编和静态联编
动态联编:编译时刻不能确定调用哪个
条件:父子,虚函数,指针或引用
c.f(1);//
c.f();//error ,不能直接调用,要通过基类的指针或者引用
//隐藏规则 :A--子函数同名不同参,基类为虚||同名同参基类不为虚,基类的函数被隐藏,不能直接通过子类对象直接调用基类的同名函数
//重载规则:同域,同名不同参,const
//覆盖规则:父子关系-不同域,同名同参,基类必须为虚
//用函数指针数组--把函数的地址存放在数组里
int fn0(int n){
return 0;
}
int fn1(int n){
static int(*p[n])()={fn0,fn1};
return p[!!n]+(n-1);
}
int main(){
cout<fn(n-1)+n;
}
};
void main(){
A a;
B b;
Arr[0]=&a;
Arr[1]=&b;
cout<fun(100)<
有纯虚函数类的类叫抽象类,不实例化对象,但可以定义指向抽象类的指针或引用,做家族顶尖,让子类去实现多态 =0的本质是将虚函数表指针指向nullptr
避免父类函数无法实现,而定义了对象,无法调用
Shape *p[4]={A a;B b;C c};
高质量C++编程指南
class A{//sizeof(A)=4;
virtual void fnA(){};
};
class B{//sizeof(B)=4;
virtual void fnB(){};
};
class C{//sizeof(C)=4;
virtual void fnC(){};
};
class D:public A,public B,public C {//sizeof(D)=12;
virtual void fnD(){};
}
class E:public A(){//sizeof(E)=8
virtual void fnD(){};
};
虽然加到了A部分的下面,但是并没有改变A的类的虚表,只是在D对象上的改动
//析构什么时候生效?//执行 delete对象指针
//派生类析构函数没有执行 为什么 运行时把全部的析构函数的函数名全部改为了destuctor
void test(Persen*p){
delete p;
}
void main(){
test(s);
}
//把父类的析构函数写成虚的
虚函数指针表存在于只读数据段
vfptr存储在对象中
RTTI 运行时类型识别 内有typeid运算符,可以返回这个指针或对象的实际类型,
typeid作用是运行时获得变量的类型,可以用于静态类型(编译时获得,如非引用)和动态类型(运行时识别,如引用)
//多态的3个例子
//1.
A::fn (int a=10){cout<<"A:fn a="<
一、填空题()
1.面向对象的特征__继承 封装 多态________________。
3.定义重载函数时,应在__参数列表_____上有所不同。
const mutable ++
类本身不占大小,只是告诉系统应该给对象开辟多大的空间
类的大小
空:一个字节 非空:静态,对齐,指针
接收本对象地址,静态成员函数是没有this指针的
引用传递:传递一个副本过去
指针传递:要加地址符int *P test(&p)//二级指针
Fun(int*&p2)//&p2是别名
Void Test1(int(&a)[4])//传的是整个数组
Void Test2(int a[1000])//传的是首地址,一般写int *a
Test1(arr);
Test2(arr);
三种形式:(1)用类的一个对象去初始化另一个对象时
(2)当函数的形参是类的对象时(也就是值传递时),如果是引用传递则不会调用
(3)当函数的返回值是类的对象或==引用时
7.在C++中有二种__参数传递_____方式即值传递和____引用___传递。
8.在C++中调用.C的函数需要加(兼容转化)extern”C”和#ifdef cppplusplus_______
二、完成程序题()
1.在下面程序的底画线处填上适当的字句,使该程序执行结果为60。
class base{int X;
public∶
void init (int initX){X=initX; }
int Getnum() {return X+7; } }
void main()
{base test;_
___test.init(53);
cout< } 2.分析下面程序的正确性 void test() { const int x = 10; int* p1 = &x;//error *p时会出错 const int* p2 = &x;//指向常量的指针,通过这个指针不能修改x,p2可变 int* const p3 = &x;//error 声明指针类型为const,p3不可变,指针指向的值为int型*p时出错 const int* const p3 = &x; int num[x] = { 1,2,3,4,5};//ok char str[]{ “helloworld” };//ok int b(10);//ok int c{ 20 }//ok auto int aa;//error auto bb;//bb得有值 auto pp = new auto(10);//??? } class Test { public: Test(int i = 0, int j) :m_j(j), m_i(m_j) {} void Print() { cout << m_i << " " << m_j << endl; } private: int m_i; int m_j; }; int main() { Test tst(4, 6); tst.Print();//随机值 6 Test tst1(5); Tst1.Print();//随机值 0 } 4.在下列程序的空格处填上适当的字句,使输出为:0,8,5。 # include # include class Magic{ double x; public∶ Magic(double d=0.00)∶x(fabs(d)){} //双精度绝对值 operator+(const Magic&c){return Magic(sqrt(xx+c.xc.x));} friend ostream &operator<<(ostream & os,Magic c); }; ostream &operator<<(ostream & os,Magic c){return os< void main() {Magic ma; cout< } ma 0 -8 8 Ma±3 ±4 5 三、简答题() 引用是给变量取一个别名,跟原来的变量名没有区别,指针是用来存放指向变量地址的 面向对象&面向过程 继承,多态,封装 对象创建时,会伴随生成一个隐含的指针, 函数的返回值怎么返回到调用方函数?函数返回后怎么知道从哪条语句开始继续执行? 调用函数时,会在这个被调函数的栈帧中给形参开辟内存,形参会把实参的值复制到对应形参,函数调用中的返回值是放在一个临时变量中的,这个临时变量可能存在于寄存器中,也可能在栈中预先分配的一段空间中(因为编译器根据函数拥有返回值会预先分配空间),函数返回时,再把临时变量的值拿出来,放到应赋给的值所在的空间中(如果有赋值的话)。 CPU执行程序时,并不知道整个程序的执行步骤是怎样的,完全是“走一步,看一步”。前面我们提到过,CPU都是根据PC中存放的指令地址找到要执行的语句。函数返回时,是“从哪里离开,就回到哪里”。但是当函数要从被调函数中返回时,PC怎么知道调用时是从哪里离开的呢?答案就是——将函数的“返回地址”保存起来。因为在发生函数调用时的PC值是知道的。在主调函数中的函数调用的下一条语句的地址即为当前PC值加1,也就是函数返回时需要的“返回地址”。我们只需将该返回地址保存起来,在被调函数执行完成后,要返回主调函数中时,将返回地址送到PC。这样,程序就可以往下继续执行了。 四、综合应用题() 1.分析下列程序可能的输出结果。) # include “iostream” class Test {private∶ int num; float fl; public∶ test( ); int getint( ){return num;} float getfloat( ){return fl;} ~test( ); }; test∶∶test( ); {cout<<″lnitalizing default″< num=0;fl=0.0; } test∶∶~test( ) {cout<<″Desdtructor is active″< int main( ) {test array[2]; cout< } 五、编程题() 1.写一个字符串类(String),要求至少要有赋值操作符,copy构造函数, +操作符。 模板库Standard Template Library–泛型编程 :类、函数 容器:vector 算法:sort(a,a+n,greater()) copy find(a,a+n,5) find_if(a,a+n,greter5) reverse swap() swap_ranges() 迭代器:相当于指针,对容器操作 –仿函数:把一个对象(类有重载的小括号)当作算法的函数参数放进去,是泛型编程的一个例子 getmin操作复杂度为O(1)。有push,pop,top。 唯一性指针:操作堆区 my_unique_ptr 柔性数组:CDTree 两个类对象共用类的指针 移动实现:move 完美转发:forward 频繁的系统调用(分配内存,创建进程或线程)导致程序频繁地从用户态转换到内核态,非常耗时,为提升程序性能,使用池化技术。 池化技术:提前保存大量资源,来备用资源或重复使用资源,内存池,(Socket/)线程池,连接池,对象池, 池化技术核心:用一个容器保存各种需要的对象,对连接/线程复用,控制复用数量和时间.
class String{
private:
char*str[];
public:
String(const String &other){
if(otherstring==*this){
return *this;
}
else{
m_string=new char[strlen(other.m_string)+1];
strcpy(m_String,other.m_String);
return *this;
}
}
String& operator+(String &s){
if(otherstring==*this){
return *this;
}
else{
m_string=new char[strlen(otherstring.m_string)+1];
strcpy(m_String,other.m_String);
return *this;
}
s.str=str;}
String& operator=(String &s){
s.str=str;
}
};
#include
运算符重载
//#include
STL
count(a,a+5,2) count(a,a+5,less5()) include(a,a+10,b,b+5) merge(a,a+5,b,b+5,c)string是容器类,容器类有通用算法
查find rfind
比int ret=s.compare(s1)相对应的asc11码值--= : 0 >: 1 < : -1
子串 s2=s.substr(1,4)--1:偏移量 4:个数
插 insert(0,"kkk")
擦
Student*p=new Student//p指向new出来的对象
Student*q=(Student*)malloc(sizeof(Student));
拷贝构造:用旧对象创造新对象Student s2(s1) 浅拷贝 深拷贝
浅拷贝 :只复制指向某个对象的指针,而不复制对象本身,相当于是新建了一个对象,该对象复制了原对象的指针,新旧对象还是共用一个内存块
var obj1 ={
name:'张三',
age:8,
pal:['王五','王六','王七']
}
var obj3 = shallowCopy(obj1)
function shallowCopy (src){
var newObj = {};
for(var prop in src ){
console.log(prop)
if(src.hasOwnProperty(prop)){
newObj[prop] = src[prop]
}
}
return newObj
}
obj3.name = '李四'
obj3.pal[0] = '王麻子'
console.log("obj1", obj1); //{age: 8, name: "张三", pal: ['王麻子', '王六', '王七']}
console.log("obj3", obj3); //{age: 8, name: "李四", pal: ['王麻子', '王六', '王七']}
深拷贝:是新建一个一模一样的对象,该对象与原对象不共享内存,修改新对象也不会影响原对象
var arr = ['jack',25,{hobby:'tennise'}];
let arr1 = JSON.parse(JSON.stringify(arr))
arr1[2].hobby='rose'
arr1[0]='rose'
console.log( arr[2].hobby) //tennise
console.log( arr[0]) //jack
var obj = { //原数据,包含字符串、对象、函数、数组等不同的类型
name:"test",
main:{
a:1,
b:2
},
fn:function(){
},
friends:[1,2,3,[22,33]]
}
function copy(obj){
let newobj = null; //声明一个变量用来储存拷贝之后的内容
//判断数据类型是否是复杂类型,如果是则调用自己,再次循环,如果不是,直接赋值即可,
//由于null不可以循环但类型又是object,所以这个需要对null进行判断
if(typeof(obj) == 'object' && obj !== null){
//声明一个变量用以储存拷贝出来的值,根据参数的具体数据类型声明不同的类型来储存
newobj = obj instanceof Array? [] : {};
//循环obj 中的每一项,如果里面还有复杂数据类型,则直接利用递归再次调用copy函数
for(var i in obj){
newobj[i] = copy(obj[i])
}
}else{
newobj = obj
}
console.log('77',newobj)
return newobj; //函数必须有返回值,否则结构为undefined
}
var obj2 = copy(obj)
obj2.name = '修改成功'
obj2.main.a = 100
console.log(obj)
console.log(obj2)
// 安装lodash
npm i --save lodash
// 引入lodash
var _ = require('lodash');
var obj1 ={
name:'jack',
age:25,
}
let obj2 =_.cloneDeep(obj1)
obj2.name = 'rose'
console.log('obj1',obj1.name) //jack
console.log('obj2',obj2.name) //rose
#include
//带头结点的双向循环链表
using namespace std;
#if 0
//关联容器 已序群集 set map
//vector对象有字符串成员调用构造时须调用拷贝构造
void main() {
vector<string > v;
vector<vector<string>>vv;
for (int i = 0; i < 3; i++) {
v.push_back("");
v[i].append(5, '*');
}
for (int j = 0;j < 3;j++) cout << v[j] << endl;
cout << endl;
cout << endl;
for (int i = 0; i < 3; i++) {
vv.push_back(vector<string>());
for (int j = 0;j < 4;j++) {
vv[i].push_back("12345");
cout << vv[i][j] << endl;
}
cout << endl;
}
vector<int>v2;
vector<int>::iterator iter;//迭代器定义
for (iter = v2.begin();iter != v2.end();iter++) {
cout << *iter << endl;
}
vector<int>v3;
v3.reserve(10);//--v.capacity最小容量,利用push_back可以扩容
v3.resize(5);//--v.size
}
#endif
#if 0
void main() {
vector<vector<int>>vv;
for (int i = 0; i < 3; i++) {
vv.push_back(vector<int>());
for (int j = 0;j < 4;j++) {
vv[i].push_back(4);
}
}
for (int i = 0; i < 3; i++) {
for (int j = 0;j < 4;j++) {
cout << vv[i][j];
}
cout << endl;
}
}
#endif
#if 0
int Print(int n) {
cout << n;
return n;
}
void main() {
vector<int> vv;
copy(istream_iterator<int>(cin), istream_iterator<int>(), back_insert_iterator<vector<int>>(vv));
sort(vv.begin(), vv.end());
copy(vv.begin(), vv.end(), ostream_iterator<int>(cout," "));
for_each(vv.begin(), vv.end(), Print(4));
cout << endl;
}
#endif
#if 0
template <class T>//类型种类有几个写几个class Tn
T Max(T a, T b)//函数模板
{
return a > b ? a : b;
}
void main()
{
cout << Max(1, 2) << endl;
cout << Max('a', 'b') << endl;//模板函数
}
#endif
#if 0
template <class T>
class A
{
public:
A(T i=0):m_i(i){}
void print() {cout << i << endl;}
private:
int m_i = 0;
};
int main() {
A<int> a(10);
a.print();
}
#endif
设计一个求最小值栈
class Stack{
public:
void Push(int x){
data.push(x);
if(MinStack.empty())MinStack.push(x);
else{
if(x>MinStack.top())x=MinStack.top();
MinStack.push(x);
}
}
void Pop(){
data.pop();
MinStack.pop();
}
void Top();
void GetMin(){
return MinStack.pop;
}
private:
stack
带权路径长度相同的路径
class Node{};
Class Tree{};
void findpath(Node*&node, vector
智能指针
//柔性数组 类的最后一个成员是长度为0的数组
class
线程池
多线程
int i=0;
const int n=100;
std::mutex mtx;
std::condition_variable mcv;
void funa(){
std::unique_lock
哲学家就餐
对象池的实现