4、C++快速入门2
1、抽象类
如果一个类里面有纯虚函数,其被编译器认为是一个抽象类,抽象类不能用来实例化一个对象
纯虚函数定义:virtual void 函数名(void)= 0;
抽象类是给派生类定义好接口函数,如果派生类不实现抽象类中的所有纯虚函数,这个派生类还是一个抽象类,不能实例化对象
2、抽象类界面
一个程序由多个人编写,分为:
应用编写:使用类
类编写:提供类(把提供类编译成库)
eg:Makefile内容
Human:main.o libHuman,so
g++ -o $@ $< -L./ -lHuman
%.o:%.cpp
g++ -fPIC -c -o $@ $<
libHuman.so:Englishman.o Chinese.o Human.o
g++ -shared -o $@ $^
make后执行命令:LD_LIBRARY_PATH=./ ./Human
这个时候如果修改Englishman.cpp ,make libHuman.so的时候只会重新生成libHuman.so,不会重新生成应用程序
对上面那个Makefile,如果.h和.cpp都发送了变化,这个时候如果仅make libHuman.so,运行的时候程序会崩溃,怎么样解决这种问题?
解决办法是main.c中仅包含一个头文件a.h,main中需要实例化某个对象的时候让该类提供一个函数,这个函数在a.h中声明,并在类中实现:
eg:在类中Human & CreateEnglishman(char *name,int age,char *address){ return *(new Englishman(name,age,address))};
在a.h中Human & CreateEnglishman(char *name,int age,char *address);//注意a.h是中间头问题,被main.c包含,a.h也被类所在的cpp包含
注意:通过delete 来删除CreateEnglishman返回的对象的时候,是调用huamn的析构函数,所有需要把析构函数声明为虚函数,这样能根据对象的实际情况调用相印的类的析构函数,析构函数不能是纯析构函数,否则在派生类必须要实现同名的析构函数,否则派生类还是抽象类(有纯虚函数的类都是抽象类)
3、函数模板
template<typename T>
T& mymax(T& a,T& b)
{
return (a < b)?b:a;
}
使用的方法:int ia =1,ib =2;mymax(ia,ib);
函数模块只是编译指令,一般写在头文件中,编译程序的时候,编译器根据函数的参数来“推导”模板的参数,然后生成具体的函数
比如上面的使用方法会生成如下代码:
int& mymax(int& a,int& b)
{
return (a < b)?b:a;
}
函数模版支持两中隐式转换(比如上述mymax函数模版,当传入的a和b的类型不一致时,编译器会报错推导不出函数,仅两张情况例外):
A、const转换,模块参数声明为const:T& mymax(const T&,const T&b);这个时候传入的参数可以不带const属性;
B、数组或函数指针转换:T& mymax(T& a,T& b);如果char a[]="abc";char b="cd";mymax(a,b)会出错,因为a,b在这里实际上是(char[4],char[3]),无法推导出T
如果存在多个可匹配的模块函数和普通函数
eg:template<typename T>
const T& mymax(const T& a,const T& b)
{
return (a<b)?b:a;
}
const T& mymax(T& a,T& b)
{
return (a<b)?b:a;
}
const int& mymax(const int& a,const int& b)
{
return (a<b)?b:a;
}
int main(int argc,char **argv)
{
int ia =1;int ib =2;
mymax(ia,ib);
}
函数选择过程:
A、列出所有匹配的函数:
第一个模板函数推导后的函数:mymax(const int& a,const int& b)
第二个模板函数推导后的函数:mymax(int& a,int& b)
第三个普通函数:mymax(const int& a,const int& b)
B、根据参数的转换排序:第二个模块和第三个函数排在第一位;第一个模板排在第二位
第一个模板int->const int
第二个模板int->int
第三个函数int->int
C、如果某个候选函数的参数跟调用时传入的参数跟匹配,则选择此候选函数
D、如果这些候选函数的参数匹配度相同
首先,优先选择普通函数
其次,对于多个模板函数,选择“更特化”的(更特化指的是参数更特殊、更具体、更细化)
否则出现二义性错误
4、类模块
template<typename T>
class AAA{
private:
T t;
public:
void test_func(const T &t);
void print(void);
}
template<typename T> void AAA<T>::test_func(const T &t)
{
this->t = t;
count<<t<<endl;
}
template<typename T> void AAA<T>::print(void)
{
cout<<t<<endl;
}
int main(int argc,char **argv)
{
AAA<int> a;
a.test_func(1);
a.print();
AAA<double> b;
b.test_func(1.23);
b.print();
return 0;
}
同时如果对当前的类模块不满意,也可以定做上面的类模版
在上面的基础上添加代码:
template<>
class AAA<int>{
public:
void test_func_int(const int & t)
{
cout<<t<<endl;
}
void print_int(void);
}
void AAA<int>::print_int(void)
{
cout<<"for test"<<endl;
}
重写main函数:
int main(int argc,char **argv)
{
AAA<int> a;
a.test_func_int(1);
a.print_int();
AAA<double> b;
b.test_func(1.23);
b.print();
return 0;
}
5、异常
三个函数A、B、C,其中A调用B,B调用C
C()
{
return XX;
}
B()
{
if(C()){}
else return -Err;
}
A()
{
if(B()){}
else {}
}
如果C出错了,A需要去处理这个错误,就需要一级一级的判断,这样如果存在N级调用的时候会非常麻烦,在C++中通过异常来处理该问题
函数A捕获函数C发出的异常,举例如下:
void C(){
throw 1;//抛出异常后直接跳到A中的catch处执行了,如果这里抛出的不是int类型的数据,程序会崩溃
}
void B(){
cout<<"call c...."<<endl;
C();
cout<<"After call c...."<<endl;//当在C中抛出异常后,这条语句不会在执行
}
void A(){
try{
B{}
}catch (int i)
{
cout<<"catch exception "<<i<<endl
}
//catch (...){}//这里的省略号表示所有异常
}
当抛出了个派生类的时候,如果catch(基类),会发生隐式转换,catch能捕获异常。
可以在函数后面声明这个函数会抛出那些异常,如果其throw非声明异常,程序会崩溃,不管有没相印的catch:eg :void C() throw(int,double){}
抛出异常如果函数没处理,其实际上是交给系统来处理了,会执行2个函数“unexpected”和“terminate”,unexpected函数用来处理声明之外的异常,terminate函数用来处理catch分支未捕获到的异常,如果都没有会两者都调用
可以通过下面语句修改这个默认的处理函数
eg:set_unexpected(异常处理函数);
也可以设置终止函数:
eg:set_terminate(终止函数名称);
6、智能指针(下面的sp就是只能指针)
void test_func(void) {
Person *p = new Person();
}
int main(int argc,char **argv)
{
test_func();//没有释放new的Person对象,会造成内存泄漏,知道main函数退出
}
如果test_func改为{Person per;}//局部变量,函数退出时对象就会被释放,调用其析构函数
使用指针也能不会造成内存泄漏的方法:
方法1:
class sp{
private:
Person *p;
public:
sp(Person *other)
{
p =other;
}
~sp()
{
if(p)
delete p;
}
}
void test_func(void) {
sp s = new Person();
}
int main(int argc,char **argv)
{
test_func();
}
对方法一进行修改:
class sp{
private:
Person *p;
public:
sp():p(0){}//构造函数p的默认值是0
sp(Person *other)
{
p =other;
}
~sp()
{
if(p)
delete p;
}
Person *operator->()//重载->
{
return p;
}
}
void test_func(void) {
sp s = new Person();
s->printInfo();//调用的是Person里面的函数,因为“->”重载了
}
int main(int argc,char **argv)
{
int i;
for(i =0;i<2;i++)
test_func();
}
再次修改:
class sp{
private:
Person *p;
public:
sp():p(0){}
sp(Person *other)
{
p =other;
}
sp(sp &other)
{
p =other.p;
}
~sp()
{
if(p)
delete p;
}
Person *operator->()//重载->
{
return p;
}
}
void test_func(sp &other) {
sp s = other;
s->printInfo();//调用的是Person里面的函数,因为“->”重载了
}
int main(int argc,char **argv)
{
int i;
sp other = new Person()//编译会出错
/*
上面这句代码相当与:
A、Person *p = new Person()
B、sp tmp(p);相当于调用sp(Person *other)构造函数
C、两种可能:1、sp other(tmp);相当于调用sp(sp &other)构造函数
出问题的愿意是sp & other这个引用来指向tmp这个临时变量,这是不对的,引用没法执行一个临时变量;但是const sp &other可以指向tmp,所以修改sp(sp &other)构造函数为sp(const sp &other)
2、tmp转换成Person*,在调用sp(Person *other)构造函数来生成other
tmp是一个sp对象,其没办法隐式转换成Person *指针
*/
test_func(other );
}
执行结果正确;
但是如果main函数改为下面,程序运行会崩溃:
int main(int argc,char **argv)
{
int i;
sp other = new Person();
for(i=0;i<2;i++)
test_func(other ); //原因是第一次退出test_func的时候会释放掉new分配的Person空间,第二次循环的时候在次调用s->printInfo();时候,Person对象已经不存在了,可以通过在Person对象中加一个引用计数,并添加获取、加1、减1三个函数,同时在sp类中所有的构造函数都调用加1函数,析构函数中先调用减1函数,接着调用获取函数来得到引用计数的值,如果为零,就调用delete p来释放,并把p设置为NULL。
}
同时还可以在sp中重载“*”
eg:Person& operator*()
{
return *p;
}
在main中
sp other = new Person();
(*other).printlnfo();
可以把sp定义为类模块:
template<typename T>
class sp{
private:
T *p;
public:
sp():p(0){}
sp(T *other)
{
p = other;
p->incStrong();
}
sp(const sp &other)
{
p = other.p;
p->incStrong();
}
~sp()
{
if(p)
{
p->decStrong();
if(p->getStrongCount() == 0)
{
delete p;
p = NULL;
}
}
}
T *operator->()
{
return p;
}
T& operator*()
{
return *p;
}
}
template<typename T>
void test_FUNC(SP<T> &other)
{
sp<T> s =other;
s->printInfo();
}
int main(int argc,char **argv)
{
sp<Person> other = new Person();
test_func(other);
}
7、Android轻量级指针(在源码RefBase.h中,sp的相关源码在StrongPointer.h中)
对上面的代码,引用计数的加1和减1操作不是原子的,在多线程的时候存在风险,在Android源码的RefBase.h中,其也是维护了一个引用计数,其加1和减1如下:
void incStrong()
{
__sync_fech_and_add(&mCount,1);
}
void decStrong()
{
//__sync_fetch_and_sub(&mCount,1)返回的是减一之前的指
if(__sync_fetch_and_sub(&mCount,1)==1){
delete static_case<const T*>(this);
}
}
修改6中的代码继承源码中的智能指针:
using namespace std;
using namespace android::RSC;
class Person : public LightRefBase<Person>{//LightRefBase里面有sp的相关代码
public:
Person(){
cout<<"Person()"<<endl;
}
~Person()
{
cout<<"~Person()"<<endl;
}
void printlnfo(void)
{
cout<<"just a test function"<<endl;
}
}
template<typename T>
void test_FUNC(sp<T> &other)
{
sp<T> s =other;
s->printInfo();
}
int main(int argc,char **argv)
{
sp<Person> other = new Person();
test_func(other);
}
8、弱指针的引人
智能指针的缺陷
eg:test_func()
{
sp<Person> a = new Person(); //a指向的对象的count = 1
sp<Person> b = new Person();//b指向的对象的count = 1
a->setFather(b);//a引用了b;b所指向的count =2;
b->setSon(a);//b引用了a;a所指向的count =2;
}
test_func程序退出的时候~a和~b执行,两者的count =1,这个时候会产生内存泄漏
eg:
using namespace std;
using namespace android::RSC;
class Person : public LightRefBase<Person>{//LightRefBase里面有sp的相关代码
private:
sp<Person> father;
sp<Person> son;
public:
Person(){
cout<<"Person()"<<endl;
}
~Person()
{
cout<<"~Person()"<<endl;
}
void setFather(sp<Person> &father)
{
this->father = father;
}
void setSon(sp<Person> &son)
{
this->son= son;
}
void printlnfo(void)
{
cout<<"just a test function"<<endl;
}
}
void test_FUNC()
{
sp<Person> father = new Person();
sp<Person> son = new Person();
father ->setSon(son );
son ->setFather(father );
}
int main(int argc,char **argv)
{
test_func(other);
return 0;
}
执行结果:
Person()
Person()
如果去掉son ->setFather(father );
执行结果:
Person()
Person()
~Person()
~Person()
分析原因:如果对象里含有其他对象成员,构造时先构造其他对象成员,在构造对象本事;析构时顺序正好相反
sp<Person> father = new Person();
1、对于new Person()
1.1 Person对象里面的father被被构造,执行sp的默认构造函数inline sp():m_ptr(0){},什么都没做
1.2 Person对象里面的son被构造,执行sp的默认构造函数inline sp():m_ptr(0){},什么都没做
1.3 Person对象本身被构造
2、Person对象的指针传给“sp<Person> father”
导致sp(T*other)被调用
它增加了这个Person对象的引用计数(现在此值等于1)
sp<Person> son= new Person();
1、对于new Person()
1.1 Person对象里面的father被被构造,执行sp的默认构造函数inline sp():m_ptr(0){},什么都没做
1.2 Person对象里面的son被构造,执行sp的默认构造函数inline sp():m_ptr(0){},什么都没做
1.3 Person对象本身被构造
2、Person对象的指针传给“sp<Person> son”
导致sp(T*other)被调用
它增加了这个Person对象的引用计数(现在此值等于1)
1、setSon里面执行的是"="操作,sp的“=”符号被重载了,它会再次增加son对象的引用计数变成2了
father ->setSon(son );
1、setFather里面执行的是"="操作,sp的“=”符号被重载了,它会再次增加father对象的引用计数变成2了
son ->setFather(father );
当test_func被执行完后,father和son被析构
1、father析构的时候调用~sp():会调用对象的decStrong函数来减1引用计数,还没为0,不会delete掉对象
1、son析构的时候调用~sp():会调用对象的decStrong函数来减1引用计数,还没为0,不会delete掉对象
对于father指向的对象,其通过father ->setSon(son )引用了son对象,导致son计数变成2了,其可以决定son指向对象的生死,同理son指向的对象,其通过son->setFather(father)引用了father对象,导致father计数变成2了,其可以决定son指向对象的生死
如果想杀掉father,需要先杀掉son中的引用值,同理杀掉son,需要先杀掉father中的引用值,这样就死锁了。
强指针/强引用 A指向B,A决定B的生死
弱指针/弱引用 A指向B,A不能决定B的生死
本例子这里是强引用,导致死锁,必须引入弱引用或者弱指针,使得father ->setSon(son )时不增加引用计数就可以解决该问题。
9、强弱引用(强弱指针)的引入
在RefBase基类中有定义了两个引用计数,分别用于强弱引用
强引用的使用sp<Person>,sp的构造函数肯定是调用incStrong来增加强引用计数,析构函数调用decStrong来减少引用计数
弱引用的使用wp<Person>,wp的构造函数肯定是调用incWeak来增加强引用计数,析构函数调用decWeak来减少引用计数
eg:本例子需要复制Android源码中的RefBase.cpp到本工程中,还有一些头文件
举例person9.cpp
Makefile:
person9:person9.o RefBase.o
g++ -o $@ $^
%.o : %.cpp
g++ -c -o $@ $< -I include
clean:
rm -f *.o person9
person9.cpp
using namespace std;
using namespace android;
class Person : public RefBase{
private:
wp<Person> father;
wp<Person> son;
public:
Person(){
cout<<"Person()"<<endl;
}
~Person()
{
cout<<"~Person()"<<endl;
}
void setFather(sp<Person> &father)
{
this->father = father;
}
void setSon(sp<Person> &son)
{
this->son= son;
}
void printlnfo(void)
{
cout<<"just a test function"<<endl;
}
}
void test_FUNC()
{
sp<Person> father = new Person();
sp<Person> son = new Person();
father ->setSon(son );
son ->setFather(father );
}
int main(int argc,char **argv)
{
test_func(other);
return 0;
}
执行结果:
Person()
Person()
~Person()
~Person()
解决了强指针相互引用导致死锁的问题
eg:
修改上面的main函数:
int main(int argc,char **argv)
{
wp<Person> s = new Person();
s->printlnfo();//编译的时候会出错,因为弱指针里面“->”没有重载,也没有重载“*”,因为wp进简单引用,没有控制对象的生死,所以如果重载"->"或者“*”,在使用的时候可能对象已经释放了,存在风险,所以源码中没有重载
return 0;
}
所以要改为强指针
int main(int argc,char **argv)
{
sp<Person> s = new Person();
if(s != 0)
s->printlnfo();
return 0;
}
eg:修改person.cpp
using namespace std;
using namespace android;
class Person : public RefBase{
private:
char *name
wp<Person> father;
wp<Person> son;
public:
Person(){
cout<<"Person()"<<endl;
}
Person(char *name){
cout<<"Person(char *name)"<<endl;
this->name = name;
}
void setFather(sp<Person> &father)
{
this->father = father;
}
void setSon(sp<Person> &son)
{
this->son= son;
}
~Person()
{
cout<<"~Person()"<<endl;
}
char *getNmae(void)
{
return name;
}
void printlnfo(void)
{
sp<Person> f = father.promote();//弱指针转换成强指针
sp<Person> s = son.promote();
cout<<"I am"<<endl;
if(f != 0)
cout<<"My father is "<<f->getName()<<endl;
if(s != 0)
cout<<"My son is "<<s->getName<<endl;
}
}
void test_FUNC()
{
sp<Person> father = new Person("LiYiShi");
sp<Person> son = new Person("LiErShi");
father ->setSon(son );
son ->setFather(father );
father->printInfo();
son ->printInfo();
}
int main(int argc,char **argv)
{
test_func();
return 0;
}
执行结果:
Person(char*name)
Person(char*name)
I am LiYiShi My son is LiErShi
I am LiErShi My fatheris LiYiShi
~Person()
~Person()
本例子使用弱指针解决了相互引用导致的问题,并且将弱指针转换为强指针可以引用对象中的函数。
强引用计数永远小与弱引用计数
10、单例模式
最简单的单例模式使用一个全局指针,如果指针为空,就new一个对象,否则直接使用
eg:
class Singleton;
Singleton *gInstance;
class Singleton{
public:
static Singleton *getInstance()
{
if(NULL = gInstance)
gInstance = new Singleton;
return gInstance;
}
Singleton()
{
cout<<"singleton()"<<endl;
}
void printInfo(){cout<<"This is singleton"<<endl}
};
int main(int argc,char ** argv)
{
Singleton *s = Singleton::getInstance();
s->printInfo();
Singleton *s = Singleton::getInstance();
s->printInfo();
Singleton *s = Singleton::getInstance();
s->printInfo();
return 0;
}
执行结果:
Singleton()//只会执行一次构造函数
This is singleton
This is singleton
This is singleton
修改main函数:
void *start_routine_thread1(void *arg)
{
cout<<"this is thread 1...."<<endl
Singleton *s = Singleton::getInstance();
s->printInfo();
return NULL;
}
void *start_routine_thread2(void *arg)
{
cout<<"this is thread 2...."<<endl
Singleton *s = Singleton::getInstance();
s->printInfo();
return NULL;
}
int main(int argc,char ** argv)
{
//创建线程,在线程里也去调用Singleton::getInstance()
pthread_t thread1ID;
pthread_t thread2ID;
pthread_create(&thread1ID,NULL,start_routine_thread1,NULL);
pthread_create(&thread2ID,NULL,start_routine_thread2,NULL);
sleep();
return 0;
}
上面的例子还是存在风险,如果两个线程同时执行,存在几率调用两次构造函数,改进方法是给那段代码加上锁;
eg:
static pthread_mutex_t g_tMutex = PTHREAD_MUTEX_INITIALIZE;
class Singleton{
static Singleton *getInstance()
{
if(NULL = gInstance)
{
pthread_mutex_lock(&g_tMutex );
if(NULL = gInstance)
gInstance = new Singleton;
pthread_mutex_lock(&g_tMutex );
}
return gInstance;
}
}
同时如果有人不使用getInstance()来实例化对象,而是调用Singleton *s = new Singleton();甚至Singleton s5,这些都会调用构造函数
解决办法:把构造函数声明为private来解决
优化:
把全局变量gInstance和锁移到类内部,并声明为static
eg:
class Singleton{
private:
static Singleton *gInstance;
static pthread_mutex_t g_tMutex ;
public:
static Singleton * getInstance()
{。。。。}
。。。。
}
static在类外初始化:
Singleton *Singleton ::gInstance;
pthread_mutex_t Singleton ::g_tMutex = PTHREAD_MUTEX_INITIALIZE;
上面这种单例设计模式被称为懒汉模式(用到时才生存实例对象),对应的还有饿汉模式
饿汉模式就是在类外初始化的时候直接实例化,这个时候也不需要锁了:
Singleton *Singleton ::gInstance = new Singleton();
11、桥接模式(android源码中只要发现了impl,其肯定就是使用桥接模式)
作用:将抽象部分和它的实现部分分离,使他们都可以独立地变化
说通俗点就是在一个类里面放另一个类的指针,这个指针指向令一个对象,使用这个指针来访问对象中的函数
举例:给电脑安装操作系统
eg:using namespace std;
class OS{
public :
virtual void install() = 0;//纯虚函数
}
class LinuxOS : public OS{
public:
virtual void Install(){cout<<"Install Linux OS"<<endl;}
}
class WindowsOS : public OS{
public:
virtual void Install(){cout<<"Install Windows OS"<<endl;}
}
class Computer{
public:
virtual void printInfo() = 0;
}
class MAC:public Computer{
public:
virtual void printInfo(){cout<<"This isMac"<<endl;};
}
class MacWithLinux: public LinuxOS,public MAC {
public:
void InstallOS(){printInfo();Install();}
}
class MacWithWindows: public WindowsOS,public MAC {
public:
void InstallOS(){printInfo();Install();}
}
class Lenovo:public Computer{
public:
virtual void printInfo(){cout<<"This is Lenovo"<<endl;};
}
class LenovoWithLinux: public LinuxOS,public Lenovo {
public:
void InstallOS(){printInfo();Install();}
}
class LenovoWithWindows: public WindowsOS,public Lenovo {
public:
void InstallOS(){printInfo();Install();}
}
int main(int argc,char **argv)
{
MacWithLinux a;
a.InstallOS();
LenovoWithLinux b;
b.InstallOS();
return 0;
}
上面的这个例子太复杂了如果computer有M个派生类,OS有N个派生类,那么要维护一个M*N的派生类,对于computer和OS我们能否建立连接,也就是说搭一个桥,在computer里面建个指针指向OS就可以了
改进代码如下:
eg:using namespace std;
class OS{
public :
virtual void installOS_impl() = 0;//纯虚函数
}
class LinuxOS : public OS{
public:
virtual void installOS_impl(){cout<<"Install Linux OS"<<endl;}
}
class WindowsOS : public OS{
public:
virtual void installOS_impl(){cout<<"Install Windows OS"<<endl;}
}
class Computer{
public:
virtual void printInfo() = 0;
virtual void InstallOS() = 0;
}
class MAC:public Computer{
public:
virtual void printInfo(){cout<<"This isMac"<<endl;};
MAC(OS *impl){this->impl = impl};
void InstallOS(){printinfo();impl->installOS_impl();};
private:
OS *impl;
}
class Lenovo:public Computer{
public:
virtual void printInfo(){cout<<"This is Lenovo"<<endl;};
Lenovo(OS *impl){this->impl = impl};
void InstallOS(){printinfo();impl->installOS_impl();};
private:
OS *impl;
}
int main(int argc,char **argv)
{
OS *os1 = new linuxOS();
OS *os2 = new WindowsOS();
computer *c1 = new Mac(os1 );
computer *c2 = new Lenovo(os2 );
c1->InstallOS();
c2->InstallOS();
return 0;
}
Computer OS
InstallOS(抽象部分) IntallOS_impl(实现部分)
Mac Lenovo Linux Windows
在Mac里面存放指针:OS *impl 使用的时候让其指向new Linux或者new Windows
这就是所谓的桥接关系
4、C++快速入门2的更多相关文章
- Web Api 入门实战 (快速入门+工具使用+不依赖IIS)
平台之大势何人能挡? 带着你的Net飞奔吧!:http://www.cnblogs.com/dunitian/p/4822808.html 屁话我也就不多说了,什么简介的也省了,直接简单概括+demo ...
- SignalR快速入门 ~ 仿QQ即时聊天,消息推送,单聊,群聊,多群公聊(基础=》提升)
SignalR快速入门 ~ 仿QQ即时聊天,消息推送,单聊,群聊,多群公聊(基础=>提升,5个Demo贯彻全篇,感兴趣的玩才是真的学) 官方demo:http://www.asp.net/si ...
- 前端开发小白必学技能—非关系数据库又像关系数据库的MongoDB快速入门命令(2)
今天给大家道个歉,没有及时更新MongoDB快速入门的下篇,最近有点小忙,在此向博友们致歉.下面我将简单地说一下mongdb的一些基本命令以及我们日常开发过程中的一些问题.mongodb可以为我们提供 ...
- 【第三篇】ASP.NET MVC快速入门之安全策略(MVC5+EF6)
目录 [第一篇]ASP.NET MVC快速入门之数据库操作(MVC5+EF6) [第二篇]ASP.NET MVC快速入门之数据注解(MVC5+EF6) [第三篇]ASP.NET MVC快速入门之安全策 ...
- 【番外篇】ASP.NET MVC快速入门之免费jQuery控件库(MVC5+EF6)
目录 [第一篇]ASP.NET MVC快速入门之数据库操作(MVC5+EF6) [第二篇]ASP.NET MVC快速入门之数据注解(MVC5+EF6) [第三篇]ASP.NET MVC快速入门之安全策 ...
- Mybatis框架 的快速入门
MyBatis 简介 什么是 MyBatis? MyBatis 是支持普通 SQL 查询,存储过程和高级映射的优秀持久层框架.MyBatis 消除 了几乎所有的 JDBC 代码和参数的手工设置以及结果 ...
- grunt快速入门
快速入门 Grunt和 Grunt 插件是通过 npm 安装并管理的,npm是 Node.js 的包管理器. Grunt 0.4.x 必须配合Node.js >= 0.8.0版本使用.:奇数版本 ...
- 【第一篇】ASP.NET MVC快速入门之数据库操作(MVC5+EF6)
目录 [第一篇]ASP.NET MVC快速入门之数据库操作(MVC5+EF6) [第二篇]ASP.NET MVC快速入门之数据注解(MVC5+EF6) [第三篇]ASP.NET MVC快速入门之安全策 ...
- 【第四篇】ASP.NET MVC快速入门之完整示例(MVC5+EF6)
目录 [第一篇]ASP.NET MVC快速入门之数据库操作(MVC5+EF6) [第二篇]ASP.NET MVC快速入门之数据注解(MVC5+EF6) [第三篇]ASP.NET MVC快速入门之安全策 ...
- Vue.js 快速入门
什么是Vue.js vue是法语中视图的意思,Vue.js是一个轻巧.高性能.可组件化的MVVM库,同时拥有非常容易上手的API.作者是尤雨溪,写下这篇文章时vue.js版本为1.0.7 准备 我推荐 ...
随机推荐
- amaze ui响应式表格
amaze ui响应式表格 这里的div外嵌设置格式倒是不错的选择
- 体验 Windows 系统 CVM
添加角色功能: service.msc 设置自动服务,net start telnet
- Python day4知识回顾
# -*- coding: utf_8 _*_# Author:Vi#字典是无序的 info = { 'student001':"DIO", 'student002':" ...
- Android中级第十讲--相机录像和查看系统相册图片
博客出自:http://blog.csdn.net/liuxian13183,转载注明出处! All Rights Reserved ! 录像比较简单,开始录制: myCamera.unlock(); ...
- [React] Use a Render Porp
More detail check LInk. Render Prop vs HOC: HOC version for withMouse: import React from 'react' imp ...
- <memory>(包括了auto_ptr,shared_ptr等各种指针)
Memory elements This header defines general utilities to manage dynamic memory: Allocators allocator ...
- nls 字符编码文件对应的国家语言
原文 http://ftp.twaren.net/cpatch/faq/tech/tech_nlsnt.txt * updated by Kii Ali, 12-11-2001 ftp://ftp.n ...
- js17---创建对象:构造函数式和原型组合模式、动态原型模式、稳妥构造函数式
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/stri ...
- atxserver2安装与使用
atxserver2的使用 1.首先clone atxserver2代码,此时使用pip3 install requirements后执行python main.py 会提示“ [WinError 1 ...
- C#之使用app.config可记录数据,下次打开可读取记录的数据
一.背景 如下图所示,我通过open..按键打开了某个文件,之后我再把app给关闭掉,当再次打开app的时候,在textBox.Text上显示上一次打开的文件路径.通过使用app.config可以保存 ...