C/C++进阶学习笔记

size_t是什么?

size_t是一个基本的无符号整数的C / C + +类型,** 它是sizeof操作符返回的结果类型 **, 该类型的大小可选择。因此,它可以存储在理论上是可能的任何类型的数组的最大大小。 换句话说,一个指针可以被安全地放进为size_t类型(一个例外是类的函数指针,但是这是一个特殊的情况下)。 size_t类型通常用于循环、数组索引、大小的存储和地址运算。 虽然size_t可以存储一个指针,它的目的是更好地使用另一个unsinged整数类型uintptr_t形式。 在某些情况下,使用size_t类型是更为有效,比习惯性使用无符号类型的程序会更安全。

运算符优先级口决

初等单目一二级, // 初等运算符和单目运算符分别是第1、2优先级

乘除求余加减移, // 这句里面的运算符全归为算术运算符,移表示移位

关系等于不等于, // 关系运算符(< <= > >=)

按位与来异或或, // 位运算符优先级顺序: & -> ^ -> |

逻辑与或条件弱, // 逻辑运算符优先级顺序: && -> ||,后面跟着优先级比较低(弱)的条件运算符

赋值逗号一点破。 // 赋值,逗号最低

详情请见:

http://blog.csdn.net/skywalker_leo/article/details/6237222

什么情况下成员一定要用初始化列表初始化?

【1】常量成员,因为常量只能初始化不能赋值,所以必须放在初始化列表里面

【2】引用类型,引用必须在定义的时候初始化,并且不能重新赋值,所以也要写在初始化列表里面

【3】没有默认构造函数的类类型,因为使用初始化列表可以不必调用默认构造函数来初始化,而是直接调用拷贝构造函数初始化。

static与const

const 的作用总结:

1.定义常量变量,防止变量被再次修改。

2.修饰类的成员变量

3.修饰函数形参,保护传参时参数不被修改

4.const修饰函数,防止修改非static类成员变量

5.修饰指针

6.节约内存空间,const定义的变量,系统只为它分配一次内存,而使用#define定义的常量宏,能分配好多次,

static 的作用总结:

面向过程设计:
1.静态全局变量:该变量在全局数据区分配内存;静态全局变量在声明它的整个文件都是可见的,而在文件之外是不可见的;

2.静态局部变量:静态局部变量在程序执行到该对象的声明处时被首次初始化,即以后的函数调用不再进行初始化;

3.静态函数:静态函数与普通函数不同,它只能在声明它的文件当中可见,不能被其它文件使用。

面向对象设计:
1.静态数据成员:静态数据成员是该类的所有对象所共有的

2.静态成员函数:静态成员函数不专属某个对象,是整个类的数据成员。

利用容器vecvor创建一个二维数组(opencv源码中经常用到容器vector)

#include <iostream>
#include<vector>

using namespace std;

//===================================================================
//vector类的构造函数:
//【1】explicit vector(size_type Count); 
//参数:Count(The number of elements in the constructed vector)
//【2】vector(size_type Count,const Type& Val);
//参数:Val(The value of the elements in the constructed vector.)
//===================================================================

void main()
{
	//创建二维数组方法一:
	//vector<vector<int>> rect(5);
	//for (int i = 0; i < 5; i++)
	//{
	//	rect[i].resize(5);
	//}

	//创建二维数组方法二:
	vector<vector<int> > rect(5, vector<int>(5));
	//打印二维容器中的内容
	for (int i = 0; i < rect.size() ; i++)
	{
		for (int j = 0; j < rect[0].size(); j++)
		{
			printf("%5d", rect[i][j]);
		}
		cout << endl;
	}	
}


#2016.9.24

##new 对象加括号和不加括号的区别

void main()
{
	int *p1 = new int[10];//动态申请的空间里的值是随机值
	int *p2 = new int[10]();//动态申请的空间里的值被初始化为0
	cout << *p1 << endl;//输出随机值
	cout << *p2 << endl;//输出0
}

##@以下程序的构造及析构顺序

class A
{
public:
	A()
	{
		cout << "construting A" << endl;
	}
	~A()
	{
		cout << "destruting A" << endl;
	}
};

class B
{
public:
	B()
	{
		cout << "construting B" << endl;
	}
	~B()
	{
		cout << "destruting B" << endl;
	}
};

class C
{
public:
	C()
	{
		cout << "construting C" << endl;
	}
	~C()
	{
		cout << "destruting C" << endl;
	}
};

class D
{
public:
	D()
	{
		cout << "construting D" << endl;
	}
	~D()
	{
		cout << "destruting D" << endl;
	}
};

C c;
void main()
{
	A* pa = new A();
	B b;
	static D d;
	delete pa;
}

##@美亚柏科面试总结

###@strcpy函数的实现

char* myStrcpy1(char* dst, const char* src)
{
	assert(dst != NULL&&src != NULL);
	char *ret = dst;
	while ((*dst++ = *src++) != '\0');
	return ret;
}

char* myStrcpy2(char* dst, const char* src)
{
	assert(dst != NULL&&src != NULL);
	char *ret = dst;
	memcpy(dst, src, strlen(src) + 1);
	return ret;
}

###@多重继承与虚函数表

多重继承与单继承相同的是,所有虚函数都包含在虚函数表中。

不同的是,多重继承有多个虚函数表。

当子类对父类进行重写时,在虚函数表中,子类的虚函数会覆盖父类的虚函数所在的位置。

当子类有新的虚函数时,此虚函数的位置会被加在第一个虚函数表的后面

###@关于前增(减)量和后增(减)量的问题

#include <iostream>
using namespace std;

#define func1(x) ((--x)*(--x))
#define func2(y) ((y--)*(y--))

void main()
{
	int i = 10;
	int j = func1(i);
	printf("%d,%d\n", i, j);
	int k = func2(i);
	printf("%d,%d\n", i, k);
}

答案:
8,64
6,64

总之要注意的地方就是减二次!!

###@static在C/C++中的常用方法

面向过程设计:
1.静态全局变量:该变量在全局数据区分配内存;静态全局变量在声明它的整个文件都是可见的,而在文件之外是不可见的;
2.静态局部变量:静态局部变量在程序执行到该对象的声明处时被首次初始化,即以后的函数调用不再进行初始化;
3.静态函数:静态函数与普通函数不同,它只能在声明它的文件当中可见,不能被其它文件使用。

面向对象设计:
1.静态数据成员:静态数据成员是该类的所有对象所共有的
2.静态成员函数:静态成员函数不专属某个对象,是整个类的数据成员。

#2016.9.18

##为什么在MFC类库中,析构函数要写成virtual的?

因为在实现多态时,当用基类操作派生类时,能防止析构时只构构基类的析构函数而不析构派生类的析构函数。

解析:

【1】首先,把析构函数写成virtual能常在我们需要实现程序的运行时多态的情况。

【2】什么是运行时多态?就是不同类的对象在调用各自的虚函数,实现不同的操作与方法。

【3】多态性的实现是通过将基类的指针指向派生类的对象来完成的。

【4】

如果没有将析构函数声明为virtual,那么在删除这个基类的指针时,只会析构基类的析构函数,而不会析构派生类的析构函数,从而造成内存泄露

如果将析构函数声明为virtual,那么如果删除该指针,函数就会调用该指针指向的派生类的析构函数,而派生类的析构函数又自动调用基类的析构函数

#2016.9.18

##@编写String类的构造函数,析构函数,复制构造函数以及赋值函数

#include<iostream>
#include<string.h>
#include<stdio.h>
using namespace std;

//===================================================================================
//编写String类的构造函数,析构函数,复制构造函数以及赋值函数
//【1】复制构造函数和赋值运算符重载函数的参数为什么要加const?
//      a.不希望此函数对形参原版进行修改
//      b.加上const,不管实参是const或者是非const,函数都能接受。
//【2】加引用的原因?
//      避免函数调用对实参进行复制,提高效率
//【3】为什么在赋值运算符的重载函数要进行自赋值检查?
//   首先,在自赋值的时候,*this和other是同一个对象。
//       如果不进行在自赋值检查后立即返回,因为删除旧内存是一定须要的,
//       删除内存后,要调用other.m_data的strlen(),而删除内存(即删除this->m_data)后
//       因为是同一对象,所以other.m_data也被删除,所以接下来的程序会造成不可预料的错误
//===================================================================================

class String
{
        public:
                String(char* str);
                String(const String& other);
                ~String();
                String& operator=(const String& other);
        private:
                char*  m_data;
};

String::String(char* str)
{
        if(NULL==str)
        {
                m_data=new char[1];
                *m_data='\0';
        }
        else
        {
                int length=strlen(str);
                m_data=new char[length+1];
                strcpy(m_data,other.m_data);
        }
}

String::~String()
{
        delete[] m_data;
}

String& String::operator=(const String&  other)
{
        if(this==&other)//自赋值检查
                return *this;
        //释放原有的内存资源,因为调用赋值运算函数的形式为 String a("hello");String b;b=a;
        //b在构造函数时有分配一字节的内存,现在要重新分配,所以须要先释放
        delete[] m_data;
        int length=strlen(other.m_data);
        m_data=new char[length+1];
        strcpy(m_data,other.m_data);
        return *this;

}

##@关于隐式转换

#include<iostream>
#include<stdio.h>

using namespace std;

class String
{
public:
        String(const char* str)
        {
                cout<<"char construting"<<endl;
        }
         String(int n)
        {
                cout<<"int construting"<<endl;
        }
};
String func(String temp)
{
        return temp;
}
int main()
{
        String str(10);
        String str2=10;//隐式转换
        String str3='10';//隐式转换,'10'相当于ASCII码值。
        String str4=func(6);//也是隐式转换
        String str5=func("hello world");//隐式转换
        return 0;
}

详情见本博客 《C++学习笔记》。

#2016.9.13

##@为什么构造函数不能是虚函数

首先,虚函数的执行依赖于虚函数表vtable,
而在虚函数表就是在构造函数中初始化的,
怎么初始化的呢?
先初始化一个vptr,
再让它指向正确的虚函数表。

因此,如果构造函数本身就是虚函数,那么在虚函数表还没初始化的情况下,声明构造函数为虚函数完全是没有意义可言的。

#2016.9.12

##@由“MFC类库中,为什么虚析构函数是必要的”引发出的问题

因为在实现多态时,当用基类操作派生类时,能防止析构时只构构基类的析构函数而不析构派生类的析构函数。

解析:

【1】首先,把析构函数写成virtual能常在我们需要实现程序的运行时多态的情况。

【2】什么是运行时多态?就是不同类的对象在调用各自的虚函数,实现不同的操作与方法。

【3】多态性的实现是通过将基类的指针指向派生类的对象来完成的。

【4】

如果没有将析构函数声明为virtual,那么在删除这个基类的指针时,只会析构基类的析构函数,而不会析构派生类的析构函数,从而造成内存泄露

如果将析构函数声明为virtual,那么如果删除该指针,函数就会调用该指针指向的派生类的析构函数,而派生类的析构函数又自动调用基类的析构函数

//首先要懂静态联编是什么
//什么是静态联编?
//指在编译阶段就完成的联编方式。在静态联编的过程中,可根据函数类型及参数的数量的不同来确定调用哪一个函数。
#include <iostream>

using namespace std;

class A
{
public:
	void display()
	{
		cout << "display A" << endl;
	}
};

class B : public A
{
public:
	void display()
	{
		cout << "display B" << endl;
	}
};

class C : public B
{
public:
	void display()
	{
		cout << "display C" << endl;
	}
};

void main()
{
	A *ptr,a;
	B b;
	C c;
	ptr = &a;
	ptr->display();
	ptr = &b;
	//期望调用对象b的函数display(),但实际却调用基类对象a的display()
	ptr->display();
	ptr = &c;
	//期望调用对象c的函数display(),但实际却调用基类对象a的display()
	ptr->display();
}
//其次,要懂得动态联编是什么
//什么是动态联编?
//指在程序运行过程中进行的联编方式。在动态联编的过程中,只有在程序运行中才能确定要调用哪一个函数。
//在C++中,动态联编是通过继承和虚函数来实现的
//在程序运行过程中,不同类的对象调用各自的虚函数,即为运行时多态

#include <iostream>

using namespace std;

class A
{
public:
	virtual void display()
	{
		cout << "display A" << endl;
	}
};

class B : public A
{
public:
	virtual void display()//子类的virtual可省略,但为了程序可读性,一般不省略
	{
		cout << "display B" << endl;
	}
};

class C : public B
{
public:
	virtual void display()//子类的virtual可省略,但为了程序可读性,一般不省略
	{
		cout << "display C" << endl;
	}
};

void main()
{
	A *ptr,a;
	B b;
	C c;
	ptr = &a;
	ptr->display();
	ptr = &b;
	//期望调用对象b的函数display(),实际上也调用了b的display();
	ptr->display();
	ptr = &c;
	//期望调用对象c的函数display(),实际上也调用了c的display();
	ptr->display();
}
//如果没有声明析构函数为virtual
//如main中所写程序,只会调用A的析构函数
//如果声明析构函数为virtual
//那么,会依次调用C,B,A的析构函数

#include <iostream>

using namespace std;

class A
{
public:
	//virtual ~A()
	~A()
	{
		cout << "destruting A" << endl;
	}
};

class B : public A
{
public:
	~B()
	{
		cout << "destruting B" << endl;
	}
};

class C : public B
{
public:
	~C()
	{
		cout << "destruting C" << endl;
	}
};

void main()
{
	A *p = new C;
	delete p;
	p = NULL;
}

//====================总结放最后======================
//由于多态性的实现是通过将基类的指针指向派生类的对象来完成的
//如果删除该指针,就会调用该指针指向的派生类的析构函数
//而派生类的析构函数又自动调用基类的析构函数
//因此,析构函数常被声明为虚函数
//===================================================

#2016.9.9

##@static , const ,static const ,const static

【1】static 成员变量要在类外初始化
【2】const 成员变量要在构造函数的初始化列表中初始化
【3】那么 例如static const int a或者const static int a 应该在哪里进行初始化呢?
=======》实验证明:无论在类外类内,定义处就可以直接初始化。

##@成员变量的初始化(基础而且容易忽略!!)

#include <iostream>
using namespace std;

//变量的初始化顺序是根据类中的声明顺序来执行的
//因此在此例中,应该先初始化i,再j。
//所以构造函数的初始化列表中,先初始化i,然而j的值不确定,因此i的值是不确定的。
class Temp
{
public:
	//成员变量初始化 用初始化列表与直接在构造函数中初始化的初始化顺序是不同的!!
	Temp(int x)
		:j(x), i(j)
	{
		//j = x;
		//i = j;
	}
	int geti() const
	{
		return i;
	}
	int getj() const
	{
		return j;
	}
private:
	int i;
	int j;
};

int main()
{
	Temp t(8);
	cout << t.geti() << endl;
	cout << t.getj() << endl;
}

//========================Tips=============================================
//【1】
//成员变量在使用初始化列表初始化时,与构造函数中初始化成员列表的顺序无关,
//只与定义成员变量的顺序有关。
//因为成员变量的初始化次序是根据变量在内存中次序有关,
//而内存中的排列顺序早在编译期就根据变量的定义次序决定了
//【2】
//如果不使用初始化列表初始化,在构造函数内初始化时,此时与成员变量在构造函数中的位置有关。
//【3】
//类中static成员变量,必须在类外初始化。
//【4】
//类中const成员常量必须在构造函数初始化列表中初始化
//=========================================================================

#2016.9.8

##@C++中的空类,默认产生哪些类成员函数?
1.默认构造函数
2.复制构造函数
3.析构函数
4.赋值函数

##@里氏代换原则与开闭原则
里氏代换原则(Liskov Substitution Principle LSP)面向对象设计的基本原则之一, LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。
开闭原则(OCP)是面向对象设计中“可复用设计”的基石,是面向对象设计中最重要的原则之一,其它很多的设计原则都是实现开闭原则的一种手段。
##@泛型编程与模板,STL的关系
泛型程序设计是程序设计语言的一种风格或范式。泛型允许程序员在强类型程序设计语言中编写代码时使用一些以后才指定的类型,在实例化时(instantiate)作为参数指明这些类型。 Java, C#, F#, Swift, and Visual Basic .NET称之为泛型(generics);ML, Scala and Haskell称之为参数多态(parametric polymorphism);C++与D称之为模板。

###【1】什么是泛型编程?
泛型程序设计是程序设计语言的一种风格或范式,泛型允许程序员在强类型程序设计语言中编写代码时使用一些以后才指定的类型在实例化时(instantiate)作为参数指明这些类型

###【2】泛型的目的是什么?
一些强类型程序语言支持泛型,其主要目的是加强类型安全减少类转换的次数

###【3】泛型编程与模板的关系,与STL的关系?
泛型是概念, 模板是泛型的实现(C++与D称之为模板)。
泛型编程让你编写完全一般化并可重复使用的算法,其效率与针对某特定数据类型而设计的算法相同。
泛型编程的代表作品STL是一种高效、泛型、可交互操作的软件组件。

##@vector中的erase与remove

#include <iostream>
#include<vector>//容器
#include<algorithm>//算法
using namespace std;
//erase删除的是指定范围或者指定位置的内容
//remove实际上并不会删除任何内容,即不会改变vector<int> 对象的size( )
//而是改变顺序。怎么改变顺序?
//如下 1 2 3 4 1
//remove的结果:2 3 4 4 1
//首先将 1 2 3 4 1 中的1略去,变成 2 3 4 ,但又不改变其size,所以用原数组内容的末位补上
//因此:  2 3 4 + 4 1 ,返回一个指向 4的迭代器。
//再用 erase  删除 4 1。
int main()
{
	vector<int> array;
	vector<int>::iterator iter;
	vector<int>::iterator iter2;

	array.push_back(1);
	array.push_back(2);
	array.push_back(3);
	array.push_back(4);
	array.push_back(1);

	iter = array.begin();
	//remove测试
	//remove(array.begin(), array.end(), 1);
	
	//remove与erase配合使用
	array.erase(remove(array.begin(), array.end(), 6), array.end());
	
	//erase 测试
	//array.erase(array.begin()+2, array.end());
	
	for (;iter != array.end(); ++iter)
	{
		cout << *iter << endl;
	}
	return 0;
}

#2016.9.7

##@初学STL中的容器与迭代器

#include <iostream>
#include<vector>//容器
#include<algorithm>//算法
using namespace std;
//通过iterator类来遍历容器的内容
void print(vector<int> & temp)
{
	vector<int> ::iterator itera;//vector本身就是一个类,iterator是在vector类中定义的一个类
	itera = temp.begin();//如果容器有元素的话,begin返回的迭代器指向的第一个元素
	for (itera= temp.begin(); itera !=temp.end(); ++itera)//end返回的迭代器指向“最后一个元素的下一个”
	{
		cout << *itera << endl;
	}
}
int main()
{
	vector<int> num;
	//vector<int> ::const_iterator cstItera;
	int element;
	int count = 0;

	num.push_back(42);
	num.push_back(1);
	num.push_back(100);
	//num.pop_back();  //Deletes the element at the end of the vector.

	print(num);
}

#2016.9.3

##@递归函数效率低的原因
因为函数一层一层调用存在调用栈。
每次调用时要做地址保存,参数传递等,这是通过一个递归工作栈实现的。具体是每次调用函数本身要保存的内容包括:局部变量、形参、调用函数地址、返回值。那么,如果递归调用N次,就要分配N局部变量、N形参、N调用函数地址、N返回值。这势必是影响效率的。

##@运用递归的方法实现:在一个数组中,计算输出‘\0’前面所有字符的个数

#include <iostream>
using namespace std;
int getStrLen(char *buf,int num)
{
	if (num == 0||buf[0]=='\0')//如果字符为0或者第一个字符为'\0'
		return 0;
	else if (num == 1)//如果只有一个字符且第一个字符不为'\0'
	{
		return 1;
	}
	else
	{
		int t = getStrLen( buf, num / 2);
		if (t < (num / 2))
			return t;
		else
			return (t + getStrLen(buf + (num / 2), (num + 1) / 2));//最关键的地方!!
	}
}

#2016.9.2

##@auto_ptr的初步学习

为什么要使用智能指针?
我们知道c++的内存管理是让很多人头疼的事,当我们写一个new语句时,一般就会立即把delete语句直接也写了,但是我们不能避免程序还未执行到delete时就跳转了或者在函数中没有执行到最后的delete语句就返回了,如果我们不在每一个可能跳转或者返回的语句前释放资源,就会造成内存泄露

#include <iostream>
#include<string.h>
#include<memory>
using namespace std;

class simple
{
public:
	simple(int param) :number(param)
	{
		cout << "simple:" << number << endl;
	}
	~simple()
	{
		cout << "~simple:" << number << endl;
	}
	void PrintSomething()
	{
		cout << "PrintSomething:" << info_extend.c_str()<<endl;
	}
	string info_extend;
	int number;
};
int main()
{
	auto_ptr<simple> test(new simple(3));
	if (test.get())// 判断智能指针是否为空
	{
		test->PrintSomething();
		
		test.get()->info_extend = "chan";//返回所含对象 的地址,再给内部变量赋值,等价于下面的语句。
		//test->info_extend = "chan";
		test.get()->PrintSomething();

		(*test).info_extend += "weiwei";
		(*test).PrintSomething();
	}
	cout << "\n\n" << endl;
	auto_ptr<simple> test2(new simple(4));
	if (test2.get())
	{
		test2 = test;//test2完全取代test的内存管理所有权。导致 test 悬空,如果再使用会导致崩溃。
	}
	test2->PrintSomething();
	//test->PrintSomething();//使用会导致崩溃
}
//====================================================================================
//总结:
//首先:必要的头文件:#include<memory>
//定义智能指针的方法:auto_ptr<A> a (new A( ) ) 
//通过智能指针调用成员函数或成员变量的三种方法:
//【1】a.get( )->  XXX
//【2】a-> XXX
//【3】(*a) . XXX
//其中,get函数返回所含对象的地址
//====================================================================================

##@C++中打印string的方法

string s = "123";
cout << s.c_str() << endl;
//或者
printf("%s", s.c_str());

##@this指针

【1】只能在成员函数中使用,全局函数,静态函数不能使用this指针。

class A
{
public:
	int func(int p)
	{
	}
}
//******
//其中func的原型在编译器看来应该是这样的
int func(A* const this,int p);
//在实际调用中
A a;
a.func(&a,10);

【2】thisr 指针的本质是一个函数参数。this 在成员函数开始执行前构造,在成员函数执行结束后清除。生命周期和普通的局部变量的一样的。
【3】成员函数的参数不占用对象空间,因此this指针也不会占用对象空间。
【4】this指针因编译器的不同有不两的放置位置。可能在栈,堆,或者寄存器中。而在实际应用中,this指针应该是个寄存器参数。
【5】this是个指向对象 的“常指针”。所有对象能共用成员函数正是利用这个指针区别不同的变量。

##@指针与句柄

如果想更透彻一点地认识句柄,我可以告诉大家,句柄是一种指向指针的指针。我们知道,所谓指针是一种内存地址。应用程序启动后,组成这个程序的各对象是住留在内存的。如果简单地理解,似乎我们只要获知这个内存的首地址,那么就可以随时用这个地址访问对象。但是,如果您真的这样认为,那么您就大错特错了。我们知道,Windows是一个以虚拟内存为基础的操作系统。在这种系统环境下,Windows内存管理器经常在内存中来回移动对象,依此来满足各种应用程序的内存需要。对象被移动意味着它的地址变化了。如果地址总是如此变化,我们该到哪里去找该对象呢?
为了解决这个问题,Windows操作系统为各应用程序腾出一些内存储地址,用来专门登记各应用对象在内存中的地址变化,而这个地址(存储单元的位置)本身是不变的。Windows内存管理器在移动对象在内存中的位置后,把对象新的地址告知这个句柄地址来保存。这样我们只需记住这个句柄地址就可以间接地知道对象具体在内存中的哪个位置。这个地址是在对象装载(Load)时由系统分配给的,当系统卸载时(Unload)又释放给系统。[2]
句柄地址(稳定)→记载着对象在内存中的地址————→对象在内存中的地址(不稳定)→实际对象

因此,句柄与指针是两种不同的概念。
句柄用来 标记系统资源。指针用来标记物理内存地址。

##@迷途指针与空指针

当delete一个指针后,只是编译器会释放内存,但指针本身依然存在,此时它就是一个迷途指针。

当delete完成 后,使用以下语句,可以使迷途指针变为空指针

delete ptr;
ptr=NULL;

使用迷途指针和空指针都是非法的,都会造成程序崩溃。但同样是崩溃 ,迷途指针造成的崩溃是不可预料的,而空指针造成的崩溃 是可预料的。

#2016.9.1

##@要区分开来的两个程序

int a[5] = { 1,2,3,4,5 };
int *ptr = a + 1;
cout << *(ptr - 1);
int a[5] = { 1,2,3,4,5 };
int *ptr = (int*)(&a + 1);
cout << *(ptr - 1);

要点:数组名本身就是一个指针,再加个&,就变成 了双指针,这里的双指针指的就是二维数组,&a+1,就是二维数组整体加一行,ptr指向第六个元素。

##@下面的数据声明都代表什么?(函数指针与指针函数)

float **def[10];//【1】
float(**def)[10];//【2】
double*(*gh)[10];//【3】
double(*f[10])();//【4】
int*((*b)[10]);//【5】
long(*fun)(int);//【6】
int(*(*F)(int, int))(int);//【7】

//首先,得明白优先级:()>[]>*
//【1】首先,def是一个数组,那么这个数组的每个元素是什么呢?这个数组的每个元素都是一个指针。这个指针指向什么东东呢?这个指针指向另一个指针。这个“另一个指针”又指向什么?这个“另一个指针”指向的是float类型的变量。
//【2】def是一个指针。这个指针指向什么东西呢?这个指针指向“另一个指针”,而这个“另一个指针”最终指向的是一个数组。而这个数组的每个元素都是float型的变量。
//【3】gh是一个指针,这个指针指向有10个元素的一个数组。这个数组的每个元素都是一个指针,数组里面的每个元素的指针最终都分别指向一个double型的变量。
//【4】f是一个由10个元素组成的数组,这个数组的每个元素都是一个指针,而每个元素的指针最终分别都指向一个函数。这个函数不带参数,返回值的类型为double.
//【5】b为一个指针,这个指针指向一个由10个元素构成的数组,这个数组的每个元素都是一个指针,且数组的每个指针都指向一个int型的变量。
//【6】fun为一个指针,这个指针指向一个函数,这个函数带一个为int型的参数,且返回值类型为long
//【7】F为一个指针,这个指针指向函数A,函数A带二个为int型的形参,那么问题来了,函数A的返回值是什么呢?函数的返回值是一个指针,那么这个指针指向什么东西呢?这个指针指向另一个函数B,函数B带一个int型的参数,且返回值为int型。
##@写出:函数指针,函数返回指针,const指针,指向const的指针,指向const的const指针

void (*p)()
void* f
const int*
int* const 
const int* const

#2016.8.30

##@两个数组元素的地址相减

int main()
{
	int a[3] = { 1,2,3 };
	int *p, *q;
	p = a;
	q = &a[2];
	cout << q-p << endl;
}

//C语言规定:如果p已经指向数组中的一个元素,则p+1不是将p的值(地址)简单加1
//而是指向同一数组的下一个元素,即p+1实际上的地址是p+1*d
//其中d为数组元素所占的字节数。
//所以!!!!!!
//反过来,两个数组的地址相减/d 即为所求。

##@malloc/free & new/delete的区别

都用于申请动态内存和释放内存。

【1】性质与功能的区别:
性质上:malloc/free 是库函数,new/delete是运算符
功能上:new将调用constructor,而malloc不能;delete将调用destructor,而free不能。

【2】调用环境的区别:
C中常用 malloc/free , C++都是用new/delete
(malloc/freemalloc与free是C++/C 语言的标准库函数,new/delete 是C++的运算符,malloc/free要库文件支持,new/delete则不要)

【3】两者的联系
既然new/delete的功能完全覆盖了malloc/free,为什么C++还保留malloc/free呢?因为C++程序经常要调用C函数,而C程序只能用malloc/free管理动态内存。如果用free释放“new创建的动态对象”,那么该对象因无法执行析构函数而可能导致程序出错。如果用delete释放“malloc申请的动态内存”,理论上讲程序不会出错,但是该程序的可读性很差。所以new/delete、malloc/free必须配对使用。

##@ 哪个函数能成功进行两个数的交换?

#include <iostream>
//【1】
void swap1(int p,int  q)
{
	int temp = p;
	q = p;
	p = temp;
}
//【2】
void swap2(int *p, int *q)
{
	int *temp;
	*temp = *p;  
	*q= *p;
	*p = *temp;
}
//【3】
void swap3(int *p, int *q)
{
	int *temp;
	temp = p;
	p= q;
	q = temp;
}
//【4】
void swap4(int *p, int *q)
{
	int temp;
	temp = *p;
	*p = *q;
	*q = temp;
}
//【5】
void swap5(int &p, int &q)
{
	int temp;
	temp = p;
	p =q;
	q = temp;
}
int main()
{
	int a = 3;
	int b = 2;
	swap5(a, b);
	cout << a << endl << b << endl;
}

//===============================================
//【1】
//首先:此函数传入的是 值的副本
//其次:q,p为局部变量,存放在栈上
//最后:生命周期结束时,所在的栈被删除。
//=====================================================
//【2】
//*temp = *a;  // error C4700 : 使用了未初始化的局部变量“temp”
//int *temp 新建了一个指针,但不分配内存。
//但*temp=*a;系统只能“意外”分配了一段随机地址存*a指向的值。
//造成结果:“意外”分配的内存不回收,会造成内存泄露。
//=====================================================
//【3】
//要正确认识C语言中的实参与形参,形参在使用中只是实参的副本,
//若形参是指针,我们可以通过形参改变实参地址中的内容,但无法改变实参本身的值!
//这里进行改变的是形参的值,而形参只是实参的一个副本,形参本身的变化,是不能改变实参的值的!!
//简单来说,在这里,p,q 就是临时存储相应实参的地址。
//所以这里只是交换了两个局部变量临时存储的地址,对主函数的实参没影响。
//=====================================================
//【4】
//修改的是指针指向的内容。
//=====================================================
//【5】
//引用。

#2016.8.29

##@ 数组退化为指针的问题(计算sizeof问题时遇到的)

#include <iostream>
//当array[]作为参数传递过去后
//如果接收的参数是也是一个数组,那么它就会退化为一个指针
//也就是我们常说的“数组就是一个指针”。
//======================================================
//当接收的参数是一个数组引用时,
//即“数组仍然是一个数组”。
//数组引用就起到了一个保护自己退化为一个指针的作用。
using namespace std;
void each(int A[10])
{
	cout << sizeof(A) << endl;
	for (int i = 0; i<10; i++)
		cout << A[i] << " ";
	cout << endl;
}
void each2(int(&A)[10])
{
	cout<< sizeof(A) << endl;
	for (int i = 0; i<10; i++)
		cout << A[i] <<" ";
}
int main()
{
	int array[] = { 1,2,3,4,5,6,7,8,9,10 };
	each(array);//问题1:sizeof()的值?
	each2(array);//问题2:sizeof()的值?
	return 0;
}

#2016.8.28 & 2016.8.29

##@ 对数据的类型的新认识

//str的数据类型是什么?
char str[] = "abcdef";

//"abcdef"的数据类型是什么?
sizeof("abcdef");

//ptr的数据类型是什么?
char *ptr = "abcdef";

//str2的数据类型是什么?
char str2[10] = "abcdef";

//sa,ia,p的数据类型是什么?
void func(char sa[100],int ia[20],char *p); 

解:
1.char [7]
2.const char[7]
3.char*
4.char[10]
5.分别为char* int* char*

##@关于“字符串数组”和“字符串指针”的地址问题

void main()
{
	char array[] = "JMU";//     【1】
	char* ptr = "JMU";//         【2】

	printf("array is %X \n", array);
	printf("&array is %X \n", &array);
	printf("&array[0] is %X \n", &array[0]);

	printf("\nptr is %X \n", ptr);
	printf("&ptr is %X \n", &ptr);
	printf("&ptr is %X \n", &ptr[0]);
}

//【1】中:char array[] = "JMU" 的最终结果为 char array[4]={'J','M','U','\0'},
//					分配了一段长 4字节的内存。
//array         “数组名代表数组首元素的地址”   (BY 《C程序设计》)
//&array		   字符指针的地址。
//&array[0]     字符常量第一个字符的地址

//【2】中: 系统分配了两段内存:一个名为ptr,类型是一个字符指针,另外一段是一个字符串常量
//                                                  且ptr里面存放着字符常量的首地址.
//	ptr:	字符常量的首地址\
//						  ------>是两段不同的内存
//	&ptr:	字符指针的首地址/
//	&ptr[0 ]:	字符常量第一个字符的地址

##@关于“字符串可以赋值给字符指针变量”的问题的理解

char *p,a='5';
p=&a;//显然是正确的,
p="abcd";//p是指针的地址,为什么可以这样赋值????

关键点:

双引号做了3件事:
1.申请了空间(在常量区),存放了字符串
2. 在字符串尾加上了’/0’
3.返回地址( p=“abcd”; 这里就是 返回的地址 赋值给了 p )

好,接下来:

char *p = “hello”; //可以
//以下也可以,这种情况是c语言初始化所支持的
//最终结果 char a[5]={'h','e','l','l','o','\n'};
char a[ ] = “hello”; 
//但是,下面这样编译就不能通过了
//原因:谭浩强版的《C程序设计》P232“此时a为代表a数组首元素的地址,它是一个指针常量
//它的值在程序运行中是固定不变的。”,因而对a赋字符串常量的地址是错误的。
char a[10];
a = “hello”;

##@sizeof

//最基本数据类型的字节大小(32位的编译器中)
cout << sizeof(float) << endl;//4
cout << sizeof(short) << endl;//2
cout << sizeof(long) << endl;//4
cout << sizeof(long long ) << endl;//8
cout << sizeof(int) << endl;//4
cout << sizeof(double) << endl;//8
//一个指针的大小是一个定值,
//在32位编译器中,指针的大小恒为4字节;在64位编译器中,指针的大小恒为8字节。
char* q = "a";
//由具体填充值确定。
char q[]="abcdefg"
//malloc是在堆区上分配空间
char* str=(char*)malloc(100)//在堆区开辟100字节的空间
sizeof(str)=4;
//strlen 与sizeof 的区别
//【1】
char x[] = "abcd";
strlen(x)=4;//字符串的长度
sizeof(x)=5 ;//占用内存的大小
//【2】
//strlen是函数,后面必须加括号
//sizeof是操作符,后面是变量时可以不加括号(但如果是类型要加括号)


//================================================
//用sizeof计算结构体的大小的偏方!!!!
//================================================
//先估算以下两结构体的大小:
struct stu3  
{   
       char c1;   
       int i;  
       char c2;  
}  
struct stu4  
{  
       char c1;  
       char c2;  
       int i;  
 }


//答案:12;8
//简单粗暴的方法:
//步骤一:前面单元的大小必须是后面单元大小的整数倍,如果不是就补齐(即补成与后面单元的大小相同)
//步骤二:整个结构体的大小必须是最大字节的整数倍

//但还有一个知识点要注意
class A
{
public:
	static int x;
	int y;
	A()
	{}
	~A()
	{}
};
//静态变量放在全局数据区,sizeof是计算栈中分配的大小
sizeof(A)=4
//================================================
//用sizeof计算类的大小的问题
//================================================
//【1】虚函数由于要维护在虚函数表,所以要占据一个指针大小,也就是4字节
//所以要注意如果计算类的sizeof时候,看到有虚函数(无论有多少个),都是占用4个字节
//涉及到虚继承,比如说class C:virtual public B,其中B为空类,sizeof(C)=4,因为也涉及到了虚指针(虚表)
//【2】编译器会给空类隐含的添加一个字节,所以空类的sizeof为1
//【3】如果有继承关系,子类在算sizeof时候要加上父类的sizeof。
//【4】空类所占的空间为1;单一继承的空类为1;多重继承的空类空间也为1;
//【5】要注意的是sizeof计算class和计算struct是同样的道理的!!即struct{} 的sizeof也是1字节。

class A
{
	virtual void m ( )
	{
		//TODO
	}
};

class B :public A
{

};

int   main()
{
	A a;
	B b;
	cout << sizeof(A) << endl;
	cout << sizeof(B) << endl;
}

##@mutable与const

class Sample
{
//声明一个被const 修饰的函数
public:
	void SetVal(int temp) const 
	{
		a = 3;//正确,因为a被声明为mutalble
		b = 3;//错误
	}
private:
	mutable int a;
	int b;
};

##@const 与#define 有什么不同

相同:
1.都可以定义常量

不同:
1.const 常量有数据类型,而#define 没有数据类型
2.编译器可对const常量进行编译检查,而#define只是进行简单的字符替换,没有安全检查

##@关于const的问题之前没搞懂的

const BYTE* GetBuffer();//修饰函数返回值,表示函数返回的是常量

int* const a;//错误,没有初始化
int* const a=&b;//正确,必须初始化!!!!!!

#2016.8.27

##@用宏定义#define声明一个常数表示一年有多少秒

//注意点1:与typedef不同,#define不能加“ ; ”
//注意点2:括号(反正没把握都加括号吧)
//注意点3:这种涉及数值很大的宏定义要注意“溢出”的问题-》加UL!!!!
#define SECOND_PER_YEAR (365*24*60*60)UL

##@宏定义&数组与指针

//知识点1:宏定义真的真的真的就是简单的替换
//知识点2:*(&array[5]-4)=6  其中&array[5]为一个地址
//C语言规定:如果指针变量p已经指向数组中的一个元素,则p+1指向同一数组中的下一个元素而不是将p的简单地加1
#define SUB(x,y) (x-y)
//#define SUB(x,y) x-y   如果是这样,则不能通过编译
#define ACCESS_BEFORE(element,offset,value)   *SUB(&element,offset) =value

void main()
{
	int i;
	int array[10] = { 1,2,3,4,5,6,7,8,9,10 };
	//#define SUB(x,y) (x-y)->   *(&array[5]-4)=6
	//#define SUB(x,y)  x-y  ->  *&array[5]-4=6
	ACCESS_BEFORE(array[5], 4, 6);
	for (i = 0; i < 10; ++i)
	{
		printf("%d\n", array[i]);
	}
}

##@In C++ source, what is the effect of extern “C”?

the C++ compiler cannot just use the function name as a unique id to link to, so it mangles the name by adding information about the arguments. A C compiler does not need to mangle the name since you can not overload function names in C. When you state that a function has extern “C” linkage in C++, the C++ compiler does not add argument/parameter type information to the name used for linkage.

C++支持函数重载,而C不支持函数重载。
因此C++不像C,不能单靠独一无二的函数ID就能编译,C++还要利用函数的参数信息进行编译。
当声明extern "C"后,C++编译器不再利用函数的参数信息进行编译

// you can specify "C" linkage to each individual declaration/definition explicitly or use a block to group a sequence of declarations/definitions to have a certain linkage:

extern "C" void foo(int);
extern "C"
{
   void g(char);
   int i;
}
//You'll very often see code in C headers like so:

#ifdef __cplusplus
extern "C" {
#endif

// all of your legacy C code here

#ifdef __cplusplus
}
#endif

##@不用if,switch,?:等判断语句如何找出两个数中比较大(或比较小)的数?

int Max=((a+b)+abs(a-b))/2;
int Min=((a+b)-abs(a-b))/2;

##@给三个整数,写出实现能取出中间数(最大数/最小数)的函数

//对于取出中间数,最小数,最大数的题目
//若数组的元素不多(3~4个),最简单粗暴的方法就是“两两比较”!!!!
//触类旁通,根据“两两比较”的思想,也可以实现取出最大数,最小数。
//当然,对于数组元素多的,可以通过冒泡排序
#define MAX(a,b) (a>=b?a:b)
#define MIN(a,b) (a>=b?b:a)

int meddian(int a,int b,int c)
{
	//通过以下两两比较,可以剔除最小值
	int t1 = MAX(a, b);
	int t2 = MAX(a, c);
	int t3 = MAX(b, c);
	//t1,t2,t3三个数有两个数为最大值,一个为中间值
	return MIN(t1, MIN(t2, t3));
}

##@冒泡排序法(原理版&优化版)

//原理版冒泡排序
void bubbleSort1(int array[],int n)
{
	for (int i = 0; i < n; i++)
	{
		for (int j = 0; j < n - i-1; j++)
		{
			if (array[j] > array[j+1])
			{
				swap(array[j], array[j+1]);
			}
		}
	}
}
//优化版冒泡排序
void bubbleSort4(int array[], int n)
{
	int numCount = n;
	while (numCount>0)
	{
		int k = numCount;
		numCount=0;//如果没有可交换的数,这句的作用就可以使while循环结束。
		for (int j = 0; j < k - 1; j++)//比如数组有5个数,则第遍历一次要4次	
		{
			if (array[j] > array[j + 1])
			{
				swap(array[j], array[j + 1]);
				//记录下此时交换数组的位置
				//优点:比如说100个数,后90个数都是排序好的
				//那么,第一次进行交换的位置j一定在10以内
				//记录后赋给一个标志位,从此标志位之后不用再去遍历,而是遍历前10位
				//通俗来说,因为后90个已经不再须要排序,我们可以把这个数组当成只有前10位
				numCount = j + 1;//为什么+1具体逻辑可能得自己通过特殊取值法来验算
			}
		}
	}
}

#2016.8.26

##@最最常出现的面试题:一个整型数组里除了N个数字之外,其他的数字都出现了两(偶数)次,找出这N个数字(题目换成找字母也一样)

//主要思想:  ^   符合交换律,结合律  及  A^A=0,0^D=D;
//A ^ B ^ C ^ B ^ C ^ D ^ A
//= A ^ A ^ B ^ B ^ C ^ C ^ D
//= 0 ^ 0 ^ 0 ^ D
//= 0 ^ D
//= D
int find(int array[], int n)
{
	if (1 == n)
		return array[0];
	int result = 0;
	for (int i = 0; i < n; ++i)
	{
		result = result^array[i];
	}
	return result;
}

##@交换一个二进制数的奇偶位

//思想
//1.通过运算符取出奇数位
//2.通过运算符取出偶数位
//3.通过运算符结合
int ExchangeEvenOdd(int a)
{
	//针对一字节8位来说
	return ((a <<1)& 0XAA ) | ((a>>1) & 0X55);
}

##@不使用其他空间,交换两数的值

a = a^b;
b = a^b;
a = a^b;

##@用位运算实现两位数的加法

//========================================================================
//功能:用位运算实现两位数的加法
//思想:把两个数相加分为有进位的和分为没有进位
//有进位只有一种情况:11=1;(其他01=0,10=0,00=0)------->&运算符刚好符合
//没有进位: 10=1;01=1;00=0;(其他11=0)------->^运算符刚好符合
//运用递归思想,直到进位carry=0
//========================================================================
int add(int a,int b)
{
	if (0 == b)
		return a;
	int sum, carry;
	sum = a^b;//没有发生进位的位数
	carry = (a&b) << 1;//取出有发生进位的位数,再进行左移,相当于进位
	return add(sum, carry);
}

##@用位运算实现两位数的减法

//========================================================================
//功能:用位运算实现两位数的减法
//思想:8-5    <===>      8+(-5)  ,所以只要能实现减数的取反,再利用加法相加即可
//怎么取反???!!!!
//取一个数的相反数方法:取反码+1 !!!!!!!!!!!!!
//========================================================================
int sub(int a, int b)
{
	int temp = add(b, ~a+1);
	return temp;
}

##@为什么取一个相反数:取反码+1 ????

10000000 -128
10000001 -127
10000010 -126
10000011 -125
.
.
.
11111101 -3
11111110 -2
11111111 -1
00000000 0
00000001 1
00000010 2
.
.
.
01111100 124
01111101 125
01111110 126
01111111 127

##@触类旁通:用位运算取绝对值

//用“反码+1”取相反数
int multiplier = a < 0 ?  add(~a , 1) : a;
int multiplicand = b < 0 ? add(~b, 1) : b;

#2016.8.25

##@左移右移/按位与,按位或

int f(int x, int y)
{
	return (x&y) + ((x^y)>>1);
}
void main()
{
	cout << f(729, 271) << endl;
}

想法与解析:
注意点1
x&y:取的是x和y中的相同位。
x^y: 取的是x和y中的不同位。

注意点2
x&y相当于取x与y的相同位相加,不同位置X(其实无所谓,因为相同位相加即1+1,会产生进位,会把X位置1),相加完的结果右移一位(也即/2) 即为x&y

注意点3
右移相当于/2,(左移相当于2,但要注意越界溢出的问题,此题不考虑)
2016.8.24

##@(int&)a是什么意思

(int)a为将a地址的内容转化为int型
&a就是取a地址的意思
而(int&)a 则为将a的地址转化为int 型

##@boolalpha 的功能及用法
功能:将输出0,1变为输出false,true;

用法:

cout <<  (1==0) << endl; //输出0
cout << boolalpha << (1==0) << endl;//输出false

##@数组指针的指向&printf的压栈顺序问题

#include<iostream>
void main()
{
	int array[] = { 1,2,3,4,5,6,7,8 };
	int *ptr = array;
	*(ptr++) += 123;
	printf("%d\n%d\n", *ptr, *(++ptr));
}

知识点:

1.printf计算参数时是从右到左压栈的。
2.之前单纯认为数组指针*ptr 永远指向数组的第一个,这是错误的!!!!,*ptr只是在默认的时候指向数组的第一个,在程序运行的过程*ptr指向的内容会随具体程序改变!!!!

引申:

int x=5;   printf("%d %d\n",x,x++);  
int y=5;   printf("%d %d\n",y++,y);  
int z=5;   printf("%d %d %d\n",z,z++,z);  
int w=5;   printf("%d %d %d %d\n",w,++w,w++,w);
int a =5; 
printf("%d %d %d %d %d %d\n",a++, ++a, a++, ++a, a++, ++a );

1.首先得明白:printf与cout都是从右向左"计算"的,但是,“计算”并不代表“输出”!!!,解这种题,先“计算”后“输出”!!

2.然后要明白 栈和寄存器变量 的概念:x++,是将计算结果存放到栈空间,最后是要出栈的;而++x和x是将计算结果直接存放到某个寄存器变量中(是同一个),所以计算完最后输出时,++x和x的结果总是相同的。(栈空间是很多个的,而++x,x存放的寄存器是同一个。)

3.通俗来说,x++输出的是压入栈中还没自增的x值
而x,++x输出的是寄存器最终的值。

##@运算符的优先级

for (a = 0, x = 0; a <= 1 && !x++;a++)
for (a = 0, x = 0; a <= 1 && !++x;a++)

第一个for 语句,第一次就能通过。其中!优先级大于 ++
第二个for 语句,不能进入循环体。

##@运行代码结果

int func(int x)
{
	int count = 0;
	while (x)
	{
		++count;
		x = x&(x - 1);
	}
	return count;
}

//原创方法
int func(unsigned int n)
{
	static int count;
	if (NULL != n)
	{
		n = n&(n - 1);
		func(n);
		count++;
		return count;
	}
}

void main()
{
	cout << func(99) << endl;
}

程序的目的:算出一个二进制数中包含1的个数。
学习这种算法的思想!!!!!!

##@关系与/逻辑与,关系或/逻辑或

5|6=7,5&6=4,5||6=1,0&&8=0,0||8=1

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值