C++笔试总结

本文详细总结了C++笔试中的重要知识点,包括编程风格、虚函数、虚函数表、拷贝构造函数、类与结构体的内存占用、sizeof和strlen的用法、const和static的作用、重载和覆盖、null、0、nullptr的区别、动态cast和静态cast的差异,以及值传递和地址传递。此外,还深入探讨了C++中内存分配、进程间通信和线程间通信方式,数组、链表、STL容器(如vector、stack、list、queue、set、map)的使用,排序算法的时间复杂度,以及表达式求值、树的遍历、页面置换算法。文章内容全面,适合准备C++笔试的读者参考。
摘要由CSDN通过智能技术生成

C++笔试总结

编程风格

函数命名:都用大写字母开始

int PointToSL(const PointENU &point, std::string *lane_id, double *s, double *l,
              double *heading) const; 

变量命名:小写字母加下划线_

static std::unordered_map<std::string, StopSign> lane_id_stop_sign_map_;

赋值一般规范:const表示这个值不会对其改变,&就是不用再复制拷贝一遍了

------------------------------------------const 给变量赋值的一般方法
bool ReviseMapComponent::Proc(
    const std::shared_ptr<LocalizationEstimate> &localization)//代表传来的值是不能变得,只能用 
{
   
 	....
  PointENU point;
  point.set_x(x);
  point.set_y(y);
    
  std::string lane_id;
  double s = 0.0;
  double l = 0.0;
  double heading = 0.0;
  //point的值不变,只用,要给lane_id s l heading赋值,就直接取地址用指针改变值即可,或者通过引用改变值
  PointToSL(point, &lane_id, &s, &l, &heading);
    
  //或者通过引用改变值---------------------  
  //PointToSL_2(point, lane_id, s, l, heading); 
  //或者通过引用改变值---------------------   
    
  AINFO << "lane_id:" << lane_id.c_str() << "  s:" << s << "  l:" << l;
  return true;
}

//const表示这个值不会对其改变,&就是不用再复制一遍了
int ReviseMapComponent::PointToSL(const PointENU &point, std::string *lane_id,
                                  double *s, double *l, double *heading) const {
   
  ...
      
  LaneInfoConstPtr lane = nullptr;
  int ret = HDMapUtil::BaseMap().GetNearestLane(point, &lane, s, l);
  ....
      
  *lane_id = lane->id().id();
  *heading = lane->Heading(*s);
  return 0;
}

int HDMap::GetNearestLane(const common::PointENU& point,
                          LaneInfoConstPtr* nearest_lane, double* nearest_s,
                          double* nearest_l) const {
   
  return impl_.GetNearestLane(point, nearest_lane, nearest_s, nearest_l);
}

//通过引用获取值-----------------------
int ReviseMapComponent::PointToSL_2(const PointENU &point, std::string &lane_id,
                                  double &s, double &l, double &heading) const {
   ....}
虚函数

虚函数的作用是允许在派生类中重新定义与基类同名的函数,并且可以通过基类指针或引用来访问基类和派生类中的同名函数。

虚函数是C++中用于实现多态的机制。核心理念就是通过基类访问派生类定义的函数。如果父类或者祖先类中函数func()为虚函数,则子类及后代类中,函数func()是否加virtual关键字,都将是虚函数。为了提高程序的可读性,建议后代中虚函数都加上virtual关键字。

#include<iostream>  
#include<stdio.h>
using namespace std;  
  
class A  
{
     
public:  
    void foo()  
    {
     
        printf("1\n");  
    }  
    virtual void fun()  
    {
     
        printf("2\n");  
    }  
};  
class B : public A  
{
     
public:  
    void foo()  //隐藏:派生类的函数屏蔽了与其同名的基类函数  ------- 这个叫做覆盖
    {
     
        printf("3\n");  
    }  
    void fun()  //多态、覆盖            --这个叫做多态,因为有虚函数
    {
     
        printf("4\n");  
    }  
};  
int main(void)  
{
     
    A a;  
    B b;  
    A *p = &a;  
    p->foo();  //输出1
    p->fun();  //输出2
    p = &b;  
    p->foo();  //取决于指针类型,输出1
    b.foo();
    a.foo();
    p->fun();  //取决于对象类型,输出4,体现了多态
    
    a.foo();//1
    b.foo();//3
    
    a.fun();//2
    b.fun();//4
    return 0;  
} 
虚函数表
#include "stdafx.h"
#include<stdio.h>
#include<iostream>
using namespace std;

class A {
public:
	virtual void vfunc1() { cout << "A::vfunc1()" << endl; };
	virtual void vfunc2() { cout << "A::vfunc2()" << endl; };
	void func1() { cout << "A::func1()" << endl; };
	void func2() { cout << "A::func2()" << endl; };
private:
	int data1_;
	int data2_;
};

class B :public A {
public:
	virtual void vfunc1() override { cout << "B::vfunc1()" << endl; };
	void func2() { cout << "B::func2()" << endl; };
private:
	int data3_;
};

class C :public B {
public:
	virtual void vfunc1() override { cout << "C::vfunc1()" << endl; };
	void func2() { cout << "C::func2()" << endl; };
private:
	int data1_, data4_;
};

//演示了手动调用虚函数的过程
int main() {
	B a;
	typedef void(*Fun)(void);
	Fun pFun = nullptr;
	cout << "虚函数表地址:" << (int*)(&a) << endl;
	cout << "虚函数表第1个函数地址:" << (int*)*(int*)(&a) << endl;
	cout << "虚函数表第2个函数地址:" << (int*)*(int*)(&a) + 1 << endl;
	pFun = (Fun)*((int*)*(int*)(&a));
	pFun();
	pFun = (Fun)*((int*)*(int*)(&a) + 1);
	pFun();

	int c;
	scanf_s("%d",&c);
	return 0;
}

虚函数表地址:00AFFA18
虚函数表第1个函数地址:00262B40
虚函数表第2个函数地址:00262B44
B::vfunc1()
A::vfunc2()

img

拷贝构造函数

必须是引用,因为他是值传递。

编译错误。在复制构造函数中传入的参数是A的一个实例。由于是传值,把形参拷贝到实参会调用复制构造函数。因此如果允许复制构造函数传值,那么会形成永无休止的递归并造成栈溢出。因此C++的标准不允许复制构造函数传值参数,而必须是传引用或者常量引用。复制构造函数的参数需要改为:const A& other

原文链接:https://blog.csdn.net/s_lisheng/article/details/75072007

 class A{
     
private:  
        int value;  
public:  
        A(int n)  
        {
     
                value = n;  
        }  
        A(A other)  //错误c
        {
     
                value = other.value;  
        }  
        void Print()  
        {
     
                std::cout << value << std::endl;  
        }  
};  

int _tmain(int argc, _TCHAR* argv[])  
{
     
        A a = 10;  
        A b = a;  
        b.Print();  
        return 0;  
} 

引申:拷贝构造函数在哪些情况下被调用?

(1)函数的参数为类对象且参数采用值传递方式;

(2)将类对象做为函数的返回值。

class CBook {
   
public:
    CBook() {
   
        cout << "constructor is called.\n";
    }
    ~CBook() {
   
        cout << "destructor is called.\n";
    }
};
 
void invoke(CBook book) {
    // 对象作为函数参数,如果这里加了个&就不是了,因为加了&后是引用方式传递,形参和实参指向同一块地
                          // 址,就不需要创建临时对象,也就不需要调用拷贝构造函数了
    cout << "invoke is called.\n";
}
 
int main() {
   
    CBook c;
    invoke(c);
}
--------------
解答:注意拷贝构造函数在对象作为函数参数传递时被调用,注意是对象实例而不是对象引用。因此该题输出如下:
constructor is called.
invoke is called.
destructor is called. // 在invoke函数调用结束时还要释放拷贝构造函数创建的临时对象,因此这里还调用了个析构函数
destructor is called.
类占内存

总结:空的类是会占用内存空间的,而且大小是1,原因是C++要求每个实例在内存中都有独一无二的地址。(一)类内部的成员变量:普通的变量:是要占用内存的,但是要注意对齐原则(这点和struct类型很相似)。static修饰的静态变量:不占用内容,原因是编译器将其放在全局变量区。

(二)类内部的成员函数:普通函数:不占用内存。虚函数:要占用4个字节,用来指定虚函数的虚拟函数表的入口地址。所以一个类的虚函数所占用的地址是不变的,和虚函数的个数是没有关系的

#include<iostream.h>

class a {
   };
class b {
   };
class c:public a{
   
    virtual void fun()=0;
};
class d:public b,public c{
   };




int main()
{
   
    cout<<"sizeof(a)"<<sizeof(a)<<endl;
    cout<<"sizeof(b)"<<sizeof(b)<<endl;
    cout<<"sizeof(c)"<<sizeof(c)<<endl;
    cout<<"sizeof(d)"<<sizeof(d)<<endl;
    return 0;

}


程序执行的输出结果为:
sizeof(a) =1
sizeof(b)=1
sizeof(c)=4
sizeof(d)=8

结构体占内存
	union mywe
	{
   

		char ccc[18];
		int acc;
	};
	struct A
	{
   
		int a;
		double b;
		char c;
		mywe d;
	};
	struct B
	{
   
		double b;
		char c;
		int a;
	};
	struct C
	{
   
		int a;
		char c;
		double b;
		
	};

	
	A aa;
	B bb;
	C cc;
	mywe dd;
	printf("D = %d\n", sizeof(dd));//结果:D = 20
	printf("A = %d\n", sizeof(aa));//结果:A = 40
	printf("B = %d\n", sizeof(bb));//结果:B = 16
	printf("C = %d\n", sizeof(cc));//结果:C = 16
	
	cout << sizeof(int) << '\t' << sizeof(double) << '\t' << sizeof(long ) << '\t' << sizeof(short) << '\t' << sizeof(char) << '\t' << sizeof(long long) << '\t' << endl;
	// 4 8 4 2 1 8
	int a[4];//可以求出数组的大小
	cout << sizeof(a) <<'\t'<< sizeof(a) / sizeof(a[0])<<endl;
	// 16 4
	int *b = new int[4];
	cout << sizeof(b) << '\t' << sizeof(float);
	//4 4

sizeof和strlen
    char *c = "abcdef";

	char d[] = "abcdef";//带休止符的

	char e[] = {
    'a','b','c','d','e','f' };//不带休止符的



	printf("%d%d/n", sizeof(c), strlen(c));4 6

	printf("%d%d/n", sizeof(d), strlen(d));7 6

	printf("%d%d/n", sizeof(e), strlen(e));*/6 任意数
	
char a[] = "hello world";  
char *p = a;  
cout<< sizeof(a) << endl; // 12 字节  
cout<< sizeof(p) << endl; // 4 字节  
计算数组和指针的内存容量  
void Func(char a[100])  
{
     
cout<< sizeof(a) << endl; // 4 字节而不是100 字节,退化位指针  
} 
const和static的作用

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类型。以使得其返回值不为“左值”。

重载和覆盖

可作为函数重载判断依据的有:参数个数、参数类型、const修饰符;

不可以作为重载判断依据的有:返回类型。

覆盖发生在父子关系中,重载发生在一个类里面。

多态和覆盖差不多,只不过基类中要有虚函数。

null 0 nullptr区别
	int a = 10;
	int* ptr = &a;
	cout << *ptr << "   " << ptr << endl;//*ptr是他所指向的值,ptr为地址
	
	int *pptr = nullptr; 
//c语言null就是(void*0),可以隐式转换其他类型包括指针和整形,C++重载时候会发生冲突,因为可以转换为其他类型。
//C++中定义null为0,但是会和整形发生重合,所以C++就定义了nullptr,他只可以转换成指针和Bool类型,不能转换为整形
	cout << pptr << endl;//正确
	//cout << *pptr << endl;//错误
与“零”的比较语句

分别写出BOOL,int,float,指针类型的变量a

BOOL : if ( !a ) or if(a)  
int : if ( a == 0)  
float : const EXPRESSION EXP = 0.000001  
if ( a < EXP && a >-EXP)  
pointer : if ( a != NULL) or if(a == NULL)  
dynamic_cast和 static_cast的区别

C 隐式转换 显示转换

int a=0;

double b;

b=a+10;隐式转换

b=(double)a;显示转换

C++增加了四个显示转换的关键字。(C++是强类型语言)(static_cast,dynamic_cast,const_static,reinterpret_cast)

(dynamic_cast)必须要有虚函数才能进行转换,static_cast静态转换,不安全。

用法:static_cast < type-id > ( expression ),该运算符把expression转换为type-id类型,但没有运行时类型检查来保证转换的安全性

	static_cast可以做上述隐式转换的事,可以部分的做显式转换的事,也可以进行继承层次的转换。它没有运行时类型检查来保证转换的安全性。它的上行转换时没问题的,但是它的下行转换时不安全的。
      dynamic_cast几乎唯一的被用来处理多态。多态分为上行转换和下行转换**,上行转换没问题,子类的指针肯定可以转换成父类指针。下行转换就有问题了,如果要把父类的指针A转换成子类的指针B,如果A指向的是子类的对象,这样转没问题。但是,如果A指向的是父类的对象,正确的做法是转换应该为不成功的,因为多态里没有子类的指针指向父类的对象的。所以,在“A指向的是父类的对象,现要把A转成B”这种情况下,如果采用static_cast,不会报错,返回的是转换后的指针,但程序此时是不安全的。如果采用dynamic_cast,它会根据虚函数表找到A指向的是什么对象(这里要求父类必须要有虚函数,否则报错),如果是父类对象,返回NULL,如果是子类对象,返回转换后的指针,程序此时是安全的。所以,这就是dynamic_cast和static_cast最大的区别。**
      const_cast。常量指针被转化成非常量指针,并且仍然指向原来的对象;常量引用被转换成非常量引用,并且仍然指向原来的对象;常量对象被转换成非常量对象。
     reinterpret_cast。这个操作符能够在非相关的类型之间转换。操作结果只是简单的从一个指针到别的指针的值的二进制拷贝。在类型之间指向的内容不做任何类型的检查和转换

说起C++中的继承、多态、虚函数等概念,可能很多同学都有所了解,但是要说真正熟知的同学可能就不是很多了。最近在编程过程中了解到C++类型的层次转换(这就涉及到了多态和继承的相关概率),通常C语言中可以对内置类型进行强制转换,但是这样做不是很安全,在C++标准中,提供了关于类型层次转换中的两个关键字static_cast和dynamic_cast。

一、static_cast关键字(编译时类型检查)

用法:static_cast < type-id > ( expression ),该运算符把expression转换为type-id类型,但没有运行时类型检查来保证转换的安全性,它主要有如下几种用法:

(1)用于基本数据类型之间的转换,如把int转换为char,把int转换成enum,但这种转换的安全性需要开发者自己保证(这可以理解为保证数据的精度,即程序员能不能保证自己想要的程序安全),如在把int转换为char时,如果char没有足够的比特位来存放int的值(int>127或int<-127时),那么static_cast所做的只是简单的截断,及简单地把int的低8位复制到char的8位中,并直接抛弃高位。

(2)把空指针转换成目标类型的空指针

(3)把任何类型的表达式类型转换成void类型

(4)用于类层次结构中父类和子类之间指针和引用的转换。

对于以上第(4)点,存在两种形式的转换,即上行转换(子类到父类)和下行转换(父类到子类)。对于static_cast,上行转换时安全的,而下行转换时不安全的,为什么呢?因为static_cast的转换时粗暴的,它仅根据类型转换语句中提供的信息(尖括号中的类型)来进行转换,这种转换方式对于上行转换,由于子类总是包含父类的所有数据成员和函数成员,因此从子类转换到父类的指针对象可以没有任何顾虑的访问其(指父类)的成员。而对于下行转换为什么不安全,是因为static_cast只是在编译时进行类型坚持,没有运行时的类型检查,具体原理在dynamic_cast中说明。

二、dynamic_cast关键字(运行时类型检查)

用法:同static_cast

dynamic_cast主要用于类层次结构中父类和子类之间指针和引用的转换,由于具有运行时类型检查,因此可以保证下行转换的安全性,何为安全性?即转换成功就返回转换后的正确类型指针,如果转换失败,则返回NULL,之所以说static_cast在下行转换时不安全,是因为即使转换失败,它也不返回NULL。

对于上行转换,dynamic_cast和static_cast是一样的。

对于下行转换,说到下行转换,有一点需要了解的是在C++中,一般是可以用父类指针指向一个子类对象,如parent* P1 = new Children(); 但这个指针只能访问父类定义的数据成员和函数,这是C++中的静态联翩,但一般不定义指向父类对象的子类类型指针,如Children* P1 = new parent;这种定义方法不符合生活习惯,在程序设计上也很麻烦。这就解释了也说明了,在上行转换中,static_cast和dynamic_cast效果是一样的,而且都比较安全,因为向上转换的对象一般是指向子类对象的子类类型指针;而在下行转换中,由于可以定义就不同了指向子类对象的父类类型指针,同时static_cast只在编译时进行类型检查,而dynamic_cast是运行时类型检查,则需要视情况而定。下面通过代码进行说明

class Base
{
    virtual void fun(){}
};

class Derived:public Base
{
};

由于需要进行向下转换,因此需要定义一个父类类型的指针Base *P,但是由于子类继承与父类,父类指针可以指向父类对象,也可以指向子类对象,这就是重点所在。如果 P指向的确实是子类对象,则dynamic_cast和static_cast都可以转换成功,如下所示:

Base *P = new Derived();
Derived *pd1 = static_cast<Derived *>(P);
Derived *pd2 = dynamic_cast<Derived *>(P);

以上转换都能成功。
但是,如果 P 指向的不是子类对象,而是父类对象,如下所示:

Base *P = new Base;
Derived *pd3 = static_cast<Derived *>(P);
Derived *pd4 = dynamic_cast<Derived *>(P);

在以上转换中,static_cast转换在编译时不会报错,也可以返回一个子类对象指针(假想),但是这样是不安全的,在运行时可能会有问题,因为子类中包含父类中没有的数据和函数成员,这里需要理解转换的字面意思,转换是什么?转换就是把对象从一种类型转换到另一种类型,如果这时用 pd3 去访问子类中有但父类中没有的成员,就会出现访问越界的错误,导致程序崩溃。而dynamic_cast由于具有运行时类型检查功能,它能检查P的类型,由于上述转换是不合理的,所以它返回NULL。

三、总结

C++中层次类型转换中无非两种:上行转换和下行转换

对于上行转换,static_cast和dynamic_cast效果一样,都安全;

对于下行转换:你必须确定要转换的数据确实是目标类型的数据,即需要注意要转换的父类类型指针是否真的指向子类对象,如果是,static_cast和dynamic_cast都能成功;如果不是static_cast能返回,但是不安全,可能会出现访问越界错误,而dynamic_cast在运行时类型检查过程中,判定该过程不能转换,返回NULL

值传递和地址传递

参考

指针传递

指针传递本质还是值传递,

指针传递本质还是值传递,p复制了一份b的值,所以p或者b的时候,他们指向的值一样,但是是&b,和&p是不一样的,从结果中我们可以看到调用f(b)时**,传递给p的是b的内容,但是&b,和&p是不一样的**,虽然p和b都指向a。示意图如下:

img

调用f(&a)是将a的地址0x12ff44传递给p,则p就指向了a的内容,改变p后,a的内容自然就改变了

void f( int*p){
   
	printf("\n%x",&p);
	printf("\n%x",p);
	printf("\n%x\n",*p);
	*p=0xff;
}
void main()
{
   
	int a=0x10;
	printf("\n%x",&a);
	printf("\n%x\n",a);
	f(&a);
	printf("\n%x\n",a);
}

img

通过指针传递的案例我们可以看到,调用f(&a)是将a的地址0x12ff44传递给p,则p就指向了a的内容,改变p后,a的内容自然就改变了,示意图如下:

img

指针的传递

img

指针传递本质还是值传递,p复制了一份b的值,所以p或者b的时候,他们指向的值一样,但是是&b,和&p是不一样的,从结果中我们可以看到调用f(b)时**,传递给p的是b的内容,但是&b,和&p是不一样的**,虽然p和b都指向a。*但是*b ,p都是取a的内容,所以本质是一样的,都是对a进行操作.示意图如下:

img

错误案例

img

当执行strcpy(str,“Hello World!”),时会报Unhandled exception in CPoint.exe:0xC0000005:Access Violation,这是因为我们参用的是指针传递,从运行结果我们可以看到str的地址为0x12ff44,当调用Allocate(str,100)时,传递给p的是str的内容也就是0,所以p为0,但是&p并不是和&str一样的,p是str的一份拷贝,所以在运行p=(char)malloc(size)时,是给p分配的100个字节,并没有给str分配字节,所以*str还是空。所以会报错*。

改正:要么用指针的引用,或者指针的指针

img

指针的指针

void GetMemory2(char **p, int num)
{
   
  *p=(char *)malloc(sizeof(char) *num);//用malloc函数为p申请一块容量为num的类型为字符的内存
    //p里面存的是str的地址,p指向的是str,*p指向的就是str的内容,也就是要申请的那块内存
}
void Test2(void)
{
   
  char *str=NULL;
  GetMemory2(&str,100);//注意参数是&str
  strcpy(str,”hello”);
}


C和C++内存分配问题
C语言编程中的内存基本构成

C的内存基本上分为4部分:静态存储区、堆区、栈区以及常量区。他们的功能不同,对他们使用方式也就不同。

1.栈 ——由编译器自动分配释放;

2.堆 ——一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收;

3.全局区(静态区)——全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域(C++中已经不再这样划分),程序结束释放;

4.另外还有一个专门放常量的地方,程序结束释放;

(a)函数体中定义的变量通常是在栈上;

(b)用malloc, calloc, realloc等分配内存的函数分配得到的就是在堆上;

©在所有函数体外定义的是全局量;

(d)加了static修饰符后不管在哪里都存放在全局区(静态区);

(e)在所有函数体外定义的static变量表示在该文件中有效,不能extern到别的文件用;

(f)在函数体内定义的static表示只在该函数体内有效;

(g)另外,函数中的"adgfdf"这样的字符串存放在常量区。

C++编程中的内存基本构造

在C++中内存分成5个区,分别是堆、栈、全局/静态存储区、常量存储区和代码区;

1、栈,就是那些由编译器在需要的时候分配,在不需要的时候自动清楚的变量的存储区,里面的变量通常是局部变量、函数参数等。

2、堆,就是那些由new分配的内存块,他们的释放编译器不去管,由我们的应用程序去控制,一般一个new就要对应一个delete。如果程序员没有释放掉,那么在程序结束后,操作系统会自动回收。

3、全局/静态存储区,全局变量和静态变量被分配到同一块内存中,在以前的C语言中,全局变量又分为初始化的和未初始化的,在C++里面没有这个区分了,他们共同占用同一块内存区。

4、常量存储区,这是一块比较特殊的存储区,他们里面存放的是常量,不允许修改(当然,你要通过非正当手段也可以修改)。

5、代码区 (.text段),存放代码(如函数),不允许修改(类似常量存储区),但可以执行(不同于常量存储区)。

内存模型组成部分:自由存储区,动态区、静态区;

根据c/c++对象生命周期不同,c/c++的内存模型有三种不同的内存区域,即:自由存储区,动态区、静态区。

自由存储区:局部非静态变量的存储区域,即平常所说的栈;

动态区: 用new ,malloc分配的内存,即平常所说的堆;

静态区:全局变量,静态变量,字符串常量存在的位置;

注:代码虽然占内存,但不属于c/c++内存模型的一部分;

进程间通信方式和线程间通信方式
进程间通信方式:

管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。

信号量( semophore ) : 信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。

消息队列( message queue ) : 消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。

共享内存( shared memory ) :共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号两,配合使用,来实现进程间的同步和通信。

套接字( socket ) : 套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同及其间的进程通信。

线程间通信方式:

锁机制:包括互斥锁、条件变量、读写锁

​ 互斥锁提供了以排他方式防止数据结构被并发修改的方法。
​ 读写锁允许多个线程同时读共享数据,而对写操作是互斥的。
​ 条件变量可以以原子的方式阻塞进程,直到某个特定条件为真为止。对条件的测试是在互斥锁的保护下进行的。条件变量始终与互斥锁一起使用。

信号量机制(Semaphore):包括无名线程信号量和命名线程信号量

信号机制(Signal):类似进程间的信号处理

数组
初始化
int a[3] = {
   0, 1, 2}; // 正确
int a[3]={
   0,1,2,3}; //错误,初始化值个数大于数组大小
int a[3]={
   0,,2}; //错误,初始化值被跳过
int a[3]={
   0,1,}; //错误,初始化值被跳过(即使是最后一个元素,添加逗号也被认为是跳过)
int a[3]={
   0}; //正确,省略初始化最后一个元素,最后省略的元素初始化为0
int a[n]={
   0}; // 注意n必须为const类型,否则错误

    静态 int array[100];   定义了数组array,并未对数组进行初始化
    静态 int array[100] = {
   12};  定义并初始化了数组array
    动态 int* array = new int[100];  delete []array;  分配了长度为100的数组array 
    动态 int* array = new int[100](12);  delete []array; 为长度为100的数组array初始化前两个元素


-----------------------------------------

    静态 int array[10][10];  定义了数组,并未初始化
    静态 int array[10][10] = {
    {
   1,1} , {
   2,2} };  数组初始化了array[0][0,1]及array[1][0,1]
    动态 int (*array)[n] = new int[m][n]; delete []array;
    动态 int** array = new int*[m]; for(i) array[i] = new int[n];  for(i) delete []array[i]; delete []array;    多次析构
    动态 int* array = new int[m][n];  delete []array;      数组按行存储



int value[9][9]; // value[i][j]的值不定,没有初始化
int value[9][9] = {
   {
   1,1},{
   2}}//value[0][0,1]和value[1][0]的值初始化,其他初始化为0
int (*value)[n] = new int[m][n];
delete []value; // n必须为常量,调用直观。未初始化


int* a = new int[10];   //new 分配了一个大小为10的未初始化的int数组,并返回指向该数组第一个元素的指针,此指针初始化了指针a
a = {
   4, -3, 5, -2, -1, 2, 6, -2, 3}; // 错误,注意这种用大括号的数组赋值只能用在声明时,此处已经不是声明,所以出错。

例子 二位数组
	int x=0,y=0;
	

	while (cin >> x>>y)// 注意,如果输入是多个测试用例,请通过while循环处理多个测试用例
	{
   
		float** A;//以int为例

		A = new float*[x];

		for (int i = 0; i <x; i++)
		{
   
			A[i] = new float[y];
		}

		for (int i = 0; i < x; ++i)
		{
   
			for (int j = 0; j < y; ++j)
			{
   
				cin >> A[i][j];
			}

		}

		
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1.static有什么用途?(请至少说明两种) 1)在函数体,一个被声明为静态的变量在这一函数被调用过程中维持其值不变。 2) 在模块内(但在函数体外),一个被声明为静态的变量可以被模块内所用函数访问,但不能被模块外其它函数访问。它是一个本地的全局变量。 3) 在模块内,一个被声明为静态的函数只可被这一模块内的其它函数调用。那就是,这个函数被限制在声明它的模块的本地范围内使用 2.引用与指针有什么区别? 1) 引用必须被初始化,指针不必。 2) 引用初始化以后不能被改变,指针可以改变所指的对象。 3) 不存在指向空值的引用,但是存在指向空值的指针。 3.描述实时系统的基本特性 在特定时间内完成特定的任务,实时性与可靠性。 4.全局变量和局部变量在内存中是否有区别?如果有,是什么区别? 全局变量储存在静态数据库,局部变量在堆栈。 5.什么是平衡二叉树? 左右子树都是平衡二叉树 且左右子树的深度差值的绝对值不大于1。 6.堆栈溢出一般是由什么原因导致的? 没有回收垃圾资源。 7.什么函数不能声明为虚函数? constructor函数不能声明为虚函数。 8.冒泡排序算法的时间复杂度是什么? 时间复杂度是O(n^2)。 9.写出float x 与“零值”比较的if语句。 if(x>0.000001&&x<-0.000001) 10.Internet采用哪种网络协议?该协议的主要层次结构? Tcp/Ip协议 主要层次结构为: 应用层/传输层/网络层/数据链路层/物理层。 11.Internet物理地址和IP地址转换采用什么协议? ARP (Address Resolution Protocol)(地址解析協議) 12.IP地址的编码分为哪俩部分? IP地址由两部分组成,网络号和主机号。不过是要和“子网掩码”按位与上之后才能区分哪些是网络位哪些是主机位。 13.用户输入M,N值,从1至N开始顺序循环数数,每数到M输出该数值,直至全部输出。写出C程序。 循环链表,用取余操作做 14.不能做switch()的参数类型是: switch的参数不能为实型。 1.写出判断ABCD四个表达式的是否正确, 若正确, 写出经过表达式中 a的值(3分) int a = 4; (A)a += (a++); (B) a += (++a) ;(C) (a++) += a;(D) (++a) += (a++); a = ? 答:C错误,左侧不是一个有效变量,不能赋值,可改为(++a) += a; 改后答案依次为9,10,10,11 2.某32位系统下, C++程序,请计算sizeof 的值(5分). char str[] = “http://www.ibegroup.com/” char *p = str ; int n = 10; 请计算 sizeof (str ) = ?(1) sizeof ( p ) = ?(2) sizeof ( n ) = ?(3) void Foo ( char str[100]){ 请计算 sizeof( str ) = ?(4) } void *p = malloc( 100 ); 请计算 sizeof ( p ) = ?(5) 答:(1)17 (2)4 (3) 4 (4)4 (5)4 3. 回答下面的问题. (4分) (1).头文件中的 ifndef/define/endif 干什么用?预处理 答:防止头文件被重复引用 (2). #i nclude 和 #i nclude “filename.h” 有什么区别? 答:前者用来包含开发环境提供的库头文件,后者用来包含自己编写的头文件。 (3).在C++ 程序中调用被 C 编译器编译后的函数,为什么要加 extern “C”声明? 答:函数和变量被C++编译后在符号库中的名字与C语言的不同,被extern "C"修饰的变 量和函数是按照C语言方式编译和连接的。由于编译后的名字不同,C++程序不能直接调 用C 函数。C++提供了一个C 连接交换指定符号extern“C”来解决这个问题。 (4). switch()中不允许的数据类型是? 答:实型 4. 回答下面的问题(6分) (1).Void GetMemory(char **p, int num){ *p = (char *)malloc(num); } void Test(void){ char *str = NULL; GetMemory(&str, 100); strcpy(str, "hello"); printf(str); } 请问运行Test 函数会有什么样的结果? 答:输出“hello” (2). void Test(void){ char *str = (char *) malloc(100); strcpy(str, “hello”); free(str); if(str != NULL){ strcpy(str, “world”); printf(str); } } 请问运行Test 函数会有什么样的结果? 答:输出“world” (3). char *GetMemory(void){ char p[] = "hello world"; return p; } void Test(void){ char *str = NULL; str = GetMemory(); printf(str); } 请问运行Test 函数会有什么样的结果? 答:无效的指针,输出不确定 5. 编写strcat函数(6分) 已知strcat函数的原型是char *strcat (char *strDest, const char *strSrc); 其中strDest 是目的字符串,strSrc 是源字符串。 (1)不调用C++/C 的字符串库函数,请编写函数 strcat 答: VC源码: char * __cdecl strcat (char * dst, const char * src) { char * cp = dst; while( *cp ) cp++; /* find end of dst */ while( *cp++ = *src++ ) ; /* Copy src to end of dst */ return( dst ); /* return dst */ } (2)strcat能把strSrc 的内容连接到strDest,为什么还要char * 类型的返回值? 答:方便赋值给其他变量 6.MFC中CString是类型安全类么? 答:不是,其它数据类型转换到CString可以使用CString的成员函数Format来转换 7.C++中为什么用模板类。 答:(1)可用来创建动态增长和减小的数据结构 (2)它是类型无关的,因此具有很高的可复用性。 (3)它在编译时而不是运行时检查数据类型,保证了类型安全 (4)它是平台无关的,可移植性 (5)可用于基本数据类型 8.CSingleLock是干什么的。 答:同步多个线程对一个数据类的同时访问 9.NEWTEXTMETRIC 是什么。 答:物理字体结构,用来设置字体的高宽大小 10.程序什么时候应该使用线程,什么时候单线程效率高。 答:1.耗时的操作使用线程,提高应用程序响应 2.并行操作时使用线程,如C/S架构的服务器端并发线程响应用户的请求。 3.多CPU系统中,使用线程提高CPU利用率 4.改善程序结构。一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独 立的运行部分,这样的程序会利于理解和修改。 其他情况都使用单线程。 11.Windows是内核级线程么。 答:见下一题 12.Linux有内核级线程么。 答:线程通常被定义为一个进程中代码的不同执行路线。从实现方式上划分,线程有两 种类型:“用户级线程”和“内核级线程”。 用户线程指不需要内核支持而在用户程序 中实现的线程,其不依赖于操作系统核心,应用进程利用线程库提供创建、同步、调度 和管理线程的函数来控制用户线程。这种线程甚至在象 DOS 这样的操作系统中也可实现 ,但线程的调度需要用户程序完成,这有些类似 Windows 3.x 的协作式多任务。另外一 种则需要内核的参与,由内核完成线程的调度。其依赖于操作系统核心,由内核的内部 需求进行创建和撤销,这两种模型各有其好处和缺点。用户线程不需要额外的内核开支 ,并且用户态线程的实现方式可以被定制或修改以适应特殊应用的要求,但是当一个线 程因 I/O 而处于等待状态时,整个进程就会被调度程序切换为等待状态,其他线程得不 到运行的机会;而内核线程则没有各个限制,有利于发挥多处理器的并发优势,但却占 用了更多的系统开支。 Windows NT和OS/2支持内核线程。Linux 支持内核级的多线程 13.C++中什么数据分配在栈或堆中,New分配数据是在近堆还是远堆中? 答:栈: 存放局部变量,函数调用参数,函数返回值,函数返回地址。由系统管理 堆: 程序运行时动态申请,new 和 malloc申请的内存就在堆上 14.使用线程是如何防止出现大的波峰。 答:意思是如何防止同时产生大量的线程,方法是使用线程池,线程池具有可以同时提 高调度效率和限制资源使用的好处,线程池中的线程达到最大数时,其他线程就会排队 等候。 15函数模板与类模板有什么区别? 答:函数模板的实例化是由编译程序在处理函数调用时自动完成的,而类模板的实例化 必须由程序员在程序中显式地指定。 16一般数据库若出现日志满了,会出现什么情况,是否还能使用? 答:只能执行查询等读操作,不能执行更改,备份等写操作,原因是任何写操作都要记 录日志。也就是说基本上处于不能使用的状态。 17 SQL Server是否支持行级锁,有什么好处? 答:支持,设立封锁机制主要是为了对并发操作进行控制,对干扰进行封锁,保证数据 的一致性和准确性,行级封锁确保在用户取得被更新的行到该行进行更新这段时间内不 被其它用户所修改。因而行级锁即可保证数据的一致性又能提高数据操作的迸发性。 18如果数据库满了会出现什么情况,是否还能使用? 答:见16 19 关于内存对齐的问题以及sizof()的输出 答:编译器自动对齐的原因:为了提高程序的性能,数据结构(尤其是栈)应该尽可能 地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问 ;然而,对齐的内存访问仅需要一次访问。 20 int i=10, j=10, k=3; k*=i+j; k最后的值是? 答:60,此题考察优先级,实际写成: k*=(i+j);,赋值运算符优先级最低 21.对数据库的一张表进行操作,同时要对另一张表进行操作,如何实现? 答:将操作多个表的操作放入到事务中进行处理 22.TCP/IP 建立连接的过程?(3-way shake) 答:在TCP/IP协议中,TCP协议提供可靠的连接服务,采用三次握手建立一个连接。   第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状 态,等待服务器确认; 第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个 SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;   第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1) ,此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。 23.ICMP是什么协议,处于哪一层? 答:Internet控制报文协议,处于网络层(IP层) 24.触发器怎么工作的? 答:触发器主要是通过事件进行触发而被执行的,当对某一表进行诸如UPDATE、 INSERT 、 DELETE 这些操作时,数据库就会自动执行触发器所定义的SQL 语句,从而确保对数 据的处理必须符合由这些SQL 语句所定义的规则。 25.winsock建立连接的主要实现步骤? 答:服务器端:socker()建立套接字,绑定(bind)并监听(listen),用accept() 等待客户端连接。 客户端:socker()建立套接字,连接(connect)服务器,连接上后使用send()和recv( ),在套接字上写读数据,直至数据交换完毕,closesocket()关闭套接字。 服务器端:accept()发现有客户端连接,建立一个新的套接字,自身重新开始等待连 接。该新产生的套接字使用send()和recv()写读数据,直至数据交换完毕,closesock et()关闭套接字。 26.动态连接库的两种方式? 答:调用一个DLL中的函数有两种方法: 1.载入时动态链接(load-time dynamic linking),模块非常明确调用某个导出函数 ,使得他们就像本地函数一样。这需要链接时链接那些函数所在DLL的导入库,导入库向 系统提供了载入DLL时所需的信息及DLL函数定位。 2.运行时动态链接(run-time dynamic linking),运行时可以通过LoadLibrary或Loa dLibraryEx函数载入DLL。DLL载入后,模块可以通过调用GetProcAddress获取DLL函数的 出口地址,然后就可以通过返回的函数指针调用DLL函数了。如此即可避免导入库文件了 。 27.IP组播有那些好处? 答:Internet上产生的许多新的应用,特别是高带宽的多媒体应用,带来了带宽的急剧 消耗和网络拥挤问题。组播是一种允许一个或多个发送者(组播源)发送单一的数据包 到多个接收者(一次的,同时的)的网络技术。组播可以大大的节省网络带宽,因为无 论有多少个目标地址,在整个网络的任何一条链路上只传送单一的数据包。所以说组播 技术的核心就是针对如何节约网络资源的前提下保证服务质量。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值