刷题
volatile关键字告诉编译器,该变量除了可以被长须改变以外,还可以被其他代理改变。被用于硬件地址和其他并行运行的程序共享的数据。
int val1 = x;
int val2 = x;
编译器注意到使用了两次x,而没有改变值,他将x存在一个寄存器中,当val2需要x时,可以从寄存器而非初始的内存位置中读取该值以节省时间,这个过程被称为缓存(caching),。如果没有volatile关键字,编译器可能无法得知这种改变是否发生,因此,使用关键字volatile可以令编译器每次从初始的内存位置去读取该值。
字符串数组初始化的差别:
字符串数组:
const char * mytal[3] = {“hello”,“ca”,“wunai”};const char mytal2[3][10] = {“hello”,“ca”,“wunai”};
递归优点:为某些编程问题提供了最简单的解决方法
递归缺点:递归算法会很快耗尽计算机的内存资源,同时递归程序难于阅读和维护
Linux使用较多的进程间通信方式:
进程是一个独立的可调度的活动
进程是一个抽象实体,当它执行某个任务时,要分配和释放各种资源
进程是可以并行执行的计算单位
进程是资源分配的最小单元。它和程序的本质区别是:
线程:是进程内独立的一条运行路线,处理器调度的最小单元。线程可以对进程的内存空间和资源进行访问,并与同一进程中的其他线程共享。因此线程的上下文切换的开销比创建进程小很多。
线程间的同步与互斥:
union型数据所占的空间等于其最大的成员所占的空间,对union型成员的存取都从相对于该联合体基地址的偏移量为0开始,也就是说对于联合体的访问,不论是哪个变量都是从union的首地址开始,所以机器大小端的不同会对union型的数据产生影响。
大端(Big_endian)字数据的高字节存储在低地址中,字数据的低字节存储在高地址中。
小端(Little_endian)字数据的高字节存储在高地址中,字数据的低字节存储在低地址中。
使用联合体检测是大端还是小端:
#include
using namespace std;
union MyUnion
{
char a;
int b;
};
int main()
{
union MyUnion stu;
stu.b = 0; //先把联合体数据清0
stu.a = 1; // 再对小长度字节(低地址) 赋值, 判断b是否等于1,;反过来赋值b,判断小字节的值也可以(是否赋值在了低字节)。
cout << "stu: " << sizeof(stu) << "\t Addr stu: " << &stu << endl;
cout << "Num stu.a: " << (void *)stu.a << "\t Size stu.a: " << sizeof(stu.a) << "\t Addr stu.a: " << (void*)&stu.a << endl;
cout << "Num stu.b: " << (void *)stu.b << "\t Size stu.b: " << sizeof(stu.b) << "\t Addr stu.b: " << &stu.b << endl;
while (true)
{
}
return 0;
}
在函数的返回值中, void 是没有任何返回值, 而 void * 是返回任意类型的值的指针
如:
void * memcpy(void *dest, const void *src, size_t len);
void * memset ( void * buffer, int c, size_t num );
这样,任何类型的指针都可以传入memcpy和memset中,这也真实地体现了内存操作函数的意义,因为它操作的对象仅仅是一片内存,而不论这片内存是什么类型(参见C语言实现泛型编程)。
ostream为const signed char *、const unsigned char *、const char *、void *重载了<<操作符,因此,可以使用cout<<输出显示字符串;这个方法使用\0来判断是否停止显示字符。
如果要显示字符串的地址,由于传递指针输出了整个字符串,因此将其强制转换为void *类型可以显示字符串的地址。
cout << "输出与指针*********************************************************************" << endl;
int eggs = 12;
const char* amount = "dozen";
cout << &eggs; // prints address of eggs variable
cout << amount; // prints the string "dozen"
cout << (void*)amount<<endl; // prints the address of the "dozen" string
一个结构体变量定义完之后,其在内存中的存储并不等于其包含元素的宽度之和。
#include
using namespace std;
struct MyStruct
{
char a;
int b;
double c;
};
int main()
{
struct MyStruct stu;
stu.a = 1;
stu.b = 2;
stu.c = 3;
cout << "stu: " << sizeof(stu) << "\t Addr stu: " << &stu << endl;
cout << "stu.a: " << sizeof(stu.a) << "\t Addr stu.a: " << &stu.a << endl;
cout << "stu.b: " << sizeof(stu.b) << "\t Addr stu.b: " << &stu.b << endl;
cout << "stu.c: " << sizeof(stu.c) << "\t Addr stu.c: " << &stu.c << endl; // 未知原因,发现此时c地址乱码(在结构体变为c是char型时,cout认为是字符型,所以输出的是字符串,但是没有\0结尾符导致输出乱码)(解决方案:cout重载了很多类型,对于char *,它会当作以’\0’结尾的字符串来处理以便于cout输出。char a = 'c'类型取地址修改为:cout << (void *)&a << endl;)void指针可以指向任意类型的数据,就是说可以用任意类型的指针对void指针对void指针赋值。。
printf("Addr stu.c: %0X\n",&stu.c); //用此方法可以查看到c地址。
while (true)
{
}
return 0;
}
经测试,会发现sizeof(S1)= 16,其值不等于sizeof(S1.a) = 1、sizeof(S1.b) = 4和 sizeof(S1.c) = 8三者之和,这里面就存在存储对齐问题。
原则一:结构体中元素是按照定义顺序一个一个放到内存中去的,但并不是紧密排列的。从结构体存储的首地址开始,每一个元素放置到内存中时,它都会认为内存是以它自己的大小来划分的,因此元素放置的位置一定会在自己宽度的整数倍上开始(以结构体变量首地址为0计算)。如图1所示,b(int)存储时,认为内存是以int大小划分,则地址从0以4字节的步长来计算,获得此时b的地址为偏移4字节的位置。

原则二:在经过第一原则分析后,检查计算出的存储单元是否为所有元素中最宽的元素的长度的整数倍,是,则结束;若不是,则补齐为它的整数倍。

程序的栈区域是有限的,声明局部变量有时并不能满足所需的内存大小,这时就需要用户在堆区自己分配。
注意事项:内存分配完后,结束使用时一定记得释放,避免内存泄漏;如果有指针指向分配的内存,内存释放后,也需将指针释放,避免出现野指针。
const :程序会把数据当常量来处理,在声明const量的适合必须进行初始化,声明之后,不能再对它赋值。
typedef struct com{
int a;
double b;
}COM;
这样就可以使用COM来代替struct com来表示了。
使用typedef的原因之一是为经常出现的类型创建一个方便的、可识别的名称。
使用typedef来命名一个结构体类型时,可以省去结构的标记。
typedef struct{
int a;
double b;
}COM;
COM c1(3,6.1); //该语句被翻译为:struct{int a;double b;} r1 = {3,6.1};
数组初始化:
优先级问题:
其 * p++ , 其中* 和++具有相等的优先级,但结合顺序是自右向左进行的,也就是++应用于指针,而不是指针指向的元素数据。后缀形式是p++,而不是++p,即先把指针指向的元素数据提出来使用后,在指针自增1。如果是*++p的话则是先指针自增1,再取指针指向的元素数据。
(* p)++:使用p指向的数据,再使数据自增1
对指针加一,等价于对指针的值加上它所指向的对象的字节大小,如int * p ; p++; 则是p的地址移动4个字节。指针加n的话就是4乘以n个字节。指向同一数组不同位置的指针相减:差值的单位是响应类型的大小,如int数组,差值为2代表的是两个指针所指对象的距离为2个int类型的大小,不是2个字节!!!
在此看出:程序中*p++是先提取p指向的元素数据如M,然后再指针加1,此时取p的地址则是0073F961,而不是0073F960;(打印地址可以使用%p(C99标准打印地址方法,默认16进制) ,或者使用 %u , %lu甚至%d,%x都是可以的)。

int * p[5]与 int (* p)[5]的区别
C语言中,[]和()的优先级比 * 的优先级高,且他们是从左到右结合的,故 int (* p)[5]是先将 * 和p结合起来成为一个指针,该指针指向一个具有5个int值的对象。
int * p[5]定义一个指针数组p,数组内的元素是5个指针,而数组内的每一个指针指向一个int型的变量,每一个数组指针变量都和普通变量没有区别。
int (* p)[5]定义了一个指针p,p所指向的对象使包含5个int型元素的一维数组。下图可看出,p就是一个指针,指向的是二维数组中num[1]这个对象的地址,这个对象是包含5个int型元素的。即p是一个二维指针,指向二维数组,一维指针就可以指向一维数组。

同理:函数指针与指针函数:int * p()函数和int(* p)()有什么区别
int * fun(int a,int b)是一个函数,只不过返回值为指针类型(加 * 的fun函数为指针函数,其返回的指针指向一个整型变量)
#include
int main()
{
int a = 4, b = 5;
int *pluse; //指针Pluse用来接收指针函数返回的值
int* sun(int* p, int* q); //声明一个指针函数
pluse = sun(&a, &b);
printf("%d\n", *pluse);
}
int* sun(int* p, int* q)
{
int s;
s = *p + *q;
return &s; //返回s的地址
}
#include
int max(int a, int b) //取a,b的最大值
{
if (a > b) return a;
else return b;
}
int main()
{
int a = 1, b = 2;
int z;
int (*p)(int, int); //定义一个函数指针
p = max; //这个函数指针指向定义的函数
z = (*p)(a, b); // *不能少
printf("%d\n", z);
return 0;
}
由于数组名就是数组首元素的地址,所以如果实际参数是一个数组名,那么形式参数是与之相匹配的指针,在此清空下,int ar[]和int * ar 做出同样的解释,ar是指向int的指针。
声明函数时,名称可以省略,但是定义函数时,名称是不可以省略的。
以下原型等价:
指针容易搞错的点:
指针数组:主语是数组,数组里面每个元素都是指针。

数组指针:主语是指针,是一个指针,数组指针指向的是数组中的一个具体元素,它其实还是一个指针,只不过是指向数组而已;只要是一个指针指向了数组,我们就称为数组指针,常用于遍历数组。

char* ptr; // ptr是指针变量,是一个int值。 ptr指向char型变量。
在windows中,一个int占4个字节,char占1个字节,short占2个字节;
#include
void main()
{
//a是一个包含3个元素的数组,每个元素的类型是int[4]。故a[0]代表是包含4个int数值,第一个数值索引为a[0][0]。
//数组名同时也是该数组首元素的地址。
int a[3][4] = { 1,2,3,4,5,6,7,8,9,10,11,12 }; //数组下标数字也称为索引,从0开始计数。
int* ptr = a[0]; //指向a[0][0]的地址. 等同于:int* ptr = &a[0][0]; --- a[0][0]是取对应地址的值,a[0]是一个元素,是一个地址,这个元素是数组,a[0]就是元素名。
// 定义一个p指针,指向具有4个元素的一维数组
int(*p)[4] = (a + 1); //分别指向a[1][0] a[1][1] a[1][2] a[1][3]
printf("%d\n", p); //指针占用一个int型字节大小来存储指向的地址。 对指针加1,等价于对指针的值加上它指向的对象的字节大小。
printf("%d\n", *p); //*(p+n):表示寻找内存中的p指向的地址,移动n个单位(根据指向对象的字节),再取出数值。 等价于 p[n]
// *p+2 由于间接运算符*的优先级高于+,因此等价于(*p)+2 ;
//谨记, 2维数组, 需要**才取到值, 或者[][] , 或者*p[]
printf("%d\n", *(ptr + 10)); // a[0][0] 偏移10个地址, a[2][2]=11
printf("%d\n", (*p + 1)[2]); // p存储的是a[1]的地址,*p指向a[1]地址指向的地址,即a[1][0] , [2]代表取偏移2地址的数据,即 a[1][1+2] = 8
printf("%d\n", *(*(a + 2) + 1)); // a[2][1] = 10
}
浅复制:不同的对象指向同一块内存地址;故会产生相互影响。
深复制:系统开辟新的内容空间,来存储和目标对象相同的内容。
class A
{
int i;
};
class B
{
A *p;
public:
B(){p=new A;}
~B(){delete p;}
};
void sayHello(B b)
{
}
int main()
{
B b;
sayHello(b); // 执行sayHello(b);该函数接收参数b,函数中this->b= b;属于浅复制,它们指向同一块内存区域。当sayHello(b);函数运行结束时,系统删除this->b指向的内存区域,而后回到main,类b的析构函数删除b指向的内存区域,但是这块区域在sayHello(b);运行结束时,就已经被删除了,所以程序报错。
}
// 添加一个复制构造函数,实现深复制。
class B
{
A *p;
public:
B(){p=new A;}
B(const B &v){p=new A;} // 复制一个B类对象
~B(){delete p;}
};
void func(int *p)
{
static int num = 4;
p = # //地址被更改
(*p)--;
}
int main()
{
int i = 5;
int *p = &i;
func(p);//p指向的是一个地址,传地址调用
printf("%d", *p); //输出: 5 ,更改数据的不是p指向的这个地址
return 0;
}