前言
学习NDK开发需要掌握一些C/C++的基础知识
提示:以下是本篇文章正文内容,下面案例可供参考
一、指针
1. 指针的定义
数据在内存中的地址也称为指针,如果一个变量存储了一份数据的指针,我们就称它为指针变量。
int v=100;
int *pp; //指针的声明
int *ppp=&v; //指针的声明
2. 常量指针
具有只能够读取内存中数据,却不能够修改内存中数据的属性的指针,称为指向常量的指针,简称常量指针。
const int a=10;
cout << " a ==="<< a << " &a="<<&a<< endl;
int const b=20;// 一样的
///常量指针 定义:具有只能够读取内存中数据,却不能够修改内存中数据的属性的指针,称为指向常量的指针,简称常量指针。
const int *c;//常量指针 初始化可以不赋值 const修饰的是指针所指向的变量
//代表指针所指向的内存空间,不能被修改
c=&a;//正确,可以把常量的地址赋给常量指针
cout << " c ==="<< c << " *c="<<*c<< " &c="<<&c<< endl;
cout << " a ==="<< a << " &a="<<&a<< endl;
c=&b;
cout << " c ==="<< c << " *c="<<*c<< " &c="<<&c<< endl;
cout << " a ==="<< a << " &a="<<&a<< endl;
cout << "--------- a的值始终未变 c表示的指针数据变更------"<<endl;
//
// *c=30; 错误 常量指针不允许修改指针所指向的内存空间 ,间接引用常量指针不可以修改内存中的数据
打印结果
a ===10 &a=0x7ffee2d3c9b4
c ===0x7ffee2d3c9b4 *c=10 &c=0x7ffee2d3c9a8
a ===10 &a=0x7ffee2d3c9b4
c ===0x7ffee2d3c9b0 *c=20 &c=0x7ffee2d3c9a8
a ===10 &a=0x7ffee2d3c9b4
--------- a的值始终未变 c表示的指针数据变更------
3. 指针常量
指针常量是指指针所指向的位置不能改变,即指针本身是一个常量,但是指针所指向的内容可以改变。
///指针常量 定义:指针常量是指指针所指向的位置不能改变,即指针本身是一个常量,但是指针所指向的内容可以改变。
int aa=23455;
int bb=234;
cout << " aa ==="<< aa << " &aa=="<< &aa<<endl;
int *const p=&aa; //指针常量 初始化必须赋值
cout << " p ==="<< p << " *p=="<< *p<< " &p=="<< &p<<endl;
*p=256;//正确 指针表示的内容可以修改
cout << " p ==="<< p << " *p=="<< *p<< " &p=="<< &p<<endl;
cout << " aa ==="<< aa << " &aa=="<< &aa<<endl;
//p=111;p=&bb;//错误 指针本身的地址值不可修改
cout << "--------- aa的值变了 p表示的指针的值没有变化------"<<endl;
打印结果
aa ===23455 &aa==0x7ffee2d3c9a4
p ===0x7ffee2d3c9a4 *p==23455 &p==0x7ffee2d3c998
p ===0x7ffee2d3c9a4 *p==256 &p==0x7ffee2d3c998
aa ===256 &aa==0x7ffee2d3c9a4
--------- aa的值变了 p表示的指针的值没有变化------
4. const和define的异同点
- 都可以用来定义常量
- 编译器处理方式不同,define宏实在预处理阶段展开,const常量是编译运行阶段使用
- 类型和安全检查不同,define宏没有类型,不做任何类型检查 ,const常量有具体的类型,在编译阶段会执行类型检查
- 存储方式不同,define宏在定义时不会分配内存;define宏仅仅死展开,有多少地方使用,就展开多少次;const在定义时就分配内存空间(可以是堆内存也可以是栈)
int a=10;
int b=10;
#define e 10
// int aar[ a+b]= {1}; ///只有在linux下才可以编译通过 ,编译习惯不要使用这种方式
///下面都可以编译通过
const int aa=10;
const int bb=10;
int aaa[aa+bb]={1,2};
int bbb[aa+e]={1,2,3};
5. C语言中#undef的常用法
C语言中#undef的语法定义是:#undef 标识符,用来将前面定义的宏标识符取消定义。
//定义:#undef 标识符,用来将前面定义的宏标识符取消定义。
//1. 防止宏定义冲突
#define m 200
printf("MAX = %d\n", m);
#undef m
int m = 10;
printf("MAX = %d\n", m);
//在一个程序段中使用完宏定义后立即将其取消,防止在后面程序段中用到同样的名字而产生冲突。
//2 增强代码可读性
//在同一个头文件中定义结构类型相似的对象,根据宏定义不同获取不同的对象,主要用于增强代码的可读性。
6. 内联函数函数inline
定义:当函数被声明为内联函数之后,编译器会将其内联展开,而不是按通常的函数调用机制进行调用
优点:当函数体比较小时,内联函数可以令代码更加高效,对于存取函数以及其他函数体比较短,性能关键的函数,鼓励使用内联函数
缺点:滥用内联函数将导致程序变慢,内联可能使目标代码量或增或减,这取决于内联函数的大小,内联非常短小的存取通常会减少代码大小,但内联一个相当大的函数将戏剧性的增加代码大小
结论:一个较为合理的规则是,不要内联超过10行代码。谨慎对待析构函数的大小,析构函数往往比看起来要更长,因为有隐含的的成员和基类析构函数被调用。
另一个实用的经验准则: 内联那些包含循环或 switch 语句的函数常常是得不偿失 (除非在大多数情况下, 这些循环或 switch 语句从不被执行).
//内联函数
inline double square(double x){return x*x;}
int main() {
double a=5;
double c=square(a);
std::cout << "a==" << a << "c==="<< c<<std::endl;
return 0;
}
7. 函数默认参数、占位参数
C++中可以在函数声明时为参数提供一个默认值, 当函数调用时没有指定这个参数的值,编译器会自动用默认值代替
默认参数规则 ,如果默认参数出现,那么右边的都必须有默认参数
函数占位参数
占位参数只有参数类型声明,而没有参数名声明
一般情况下,在函数体内部无法使用占位参数
///函数默认参数
void fun(int a,int b=200 ){
cout << " a ==="<< a << " *b=="<< b<<endl;
}
///函数占位参数
int fun(int a,int b ,int){
cout << " a ==="<< a << " *b=="<< b<<endl;
return a+b;
}
int main() {
fun(1);
fun(1,2);
fun(1,2,3);
return 0;
}
8. 函数重载
C++ 不允许变量重名,但是允许多个函数取相同的名字,只要参数表不同即可,这叫作函数的重载
int max(double a,double b){
}
int max(double a,double b,double c){
}
int max(int a,int b){
}
9. 函数指针
- C语言函数指针的使用
///c语言函数指针的用法
int fun(){
cout << "fun="<< endl;
return 1;
}
int fun(int a,double b){
cout << " a ==="<< a << endl;
return a+1;
}
int main() {
//函数声明
int (*pFun1)()=NULL;
int (*pFun2)(int,double)=NULL;
int a,b,c;
pFun1=fun;//第一种赋值方法
a=pFun1();//第一种调用方法(推荐)
b=(*pFun1)();//第二种调用方法
cout << " a ==="<< a << "b ==="<< b<< endl;
pFun2=&fun;//第二种赋值方法(推荐)
pFun2(123,4.5);
return 0;
}
- C++函数指针的使用
///c++函数指针的用法
class Test{
public:
Test(){
cout << "构造函数调用"<< endl;
}
void fun(int a){
cout << "a"<<a<< endl;
}
int fun1(int a, char c)
{
cout<<"this is fun1 call:"<<a<<" "<<c<<endl;
return a;
}
static double fun3(char buf[])
{
cout<<"this is fun3 call:"<<buf<<endl;
return 3.14;
}
int fun4(int a,int b)const
{
cout<<"this is fun4 call:"<<a<<" "<<b<<endl;
return a;
}
};
int main() {
///普通成员函数
void (Test::*pFun)(int)=NULL;//一定要加类名
int (Test::*pFun1)(int ,char)=NULL;
pFun=&Test::fun;//需要加取地址符
pFun1=&Test::fun1;
Test test;
(test.*pFun)(3);
(test.*pFun1)(12,'1');//调用是一定要加类的对象名和*符号
///类的静态成员函数指针和c的指针的用法相同
double (*pFun3)(char buf[]) = NULL;//不需要加类名
pFun3 = Test::fun3; //可以不加取地址符号
pFun3("hello world");
pFun3 = &Test::fun3; //也可以加取地址符号
///const 函数基本和普通函数一样
int (Test::*fFun4)(int,int )const =NULL;//一定要加const
fFun4=&Test::fun4;//需要加取地址符
(test.*fFun4)(1,2);//调用
return 0;
}
10.构造函数
类的构造函数不能在类的声明时初始化,所以就有了在类的构造函数中初始化
析构函数是对象不在使用的时候,释放资源的时候调用
C++中的类需要定义与类名相同的特殊成员函数时,这种与类名相同的成员函数叫做构造函数;
构造函数可以在定义的时候有参数;
构造函数没有任何返回类型。
构造函数的调用: 一般情况下,C++编译器会自动的调用构造函数。特殊情况下,需要手工的调用构造函数。
///构造函数
class TestStructure{
public:
int number;
TestStructure(){
number=10;
cout<<"无参构造函数 "<<number <<endl;
}
TestStructure(int a){
this->number=a;
cout<<"有参构造函数 "<< number <<endl;
}
};
int main() {
TestStructure t1;//调用无参构造函数
TestStructure t2(30);//调用有参构造函数
return 0;
}
11. 拷贝构造函数
当用一个已初始化过了的自定义类类型对象去初始化另一个新构造的对象的时候,拷贝构造函数就会被自动调用,以下情况都会调用拷贝构造函数
- 一个对象以值传递的方式传入函数体
- 一个对象以值传递的方式从函数返回
- 一个对象需要通过另外一个对象进行初始化。
///拷贝构造函数
class TestCopyStructure{
private:int number;
public:
TestCopyStructure(int a){
this->number=a;
cout<<"有参构造函数 "<< number <<endl;
}
TestCopyStructure(const TestCopyStructure& b){
this->number=b.number;
cout<<"拷贝构造函数 "<< number <<endl;
}
};
int main() {
TestCopyStructure t1(100);///有参构造函数
TestCopyStructure t2=t1;///拷贝构造函数
return 0;
}
如果在类中没有显式地声明一个拷贝构造函数,那么,编译器将会自动生成一个默认的拷贝构造函数,该构造函数完成对象之间的位拷贝。位拷贝又称浅拷贝
12.堆上创建变量
int main() {
// C++中创建动态指针:
int *p;//定义一个指针类型的变量p
p =new int ;//new创建一个int类型的内存区域,然后将该区域的内存地址赋给指针变量p,
delete p;
int *p2 = new int;
delete p2;//释放 将动态内存释放,但是P指针变量还存在,并且还指向原来的位置
//在堆里从新创建一个对象(不同类型也可),会造成两个指针指向同一地址
p2=NULL;
//C中创建堆
int *p3=(int *)malloc(sizeof(10));
p3=NULL;//释放
return 0;
}
13.C++使用构造函数初始化列表初始化的情况
类对象的构造顺序:
- 分配内存,调用构造函数是,隐式/显示的初始化各数据乘员
- 进入构造函数后在构造函数中执行一般赋值与计算
1,一下三种情况必须使用初始化成员列表
a、需要初始化的数据成员是对象的情况(这里包含了继承情况下,通过显示调用父类的构造函数对父类数据成员进行初始化);
class Test12{
private:
int x;
int y;
int z;
public:
Test12(int, int, int){
cout<<" Test12 "<<endl;
}
};
//数据成员是对象,并且这个对象只有含参数的构造函数,没有无参数的构造函数
class Test12Add{
private:Test12 test12;//声明
public:
Test12Add():test12(1,2,3){ ///初始化
cout<<" Test12Add "<<endl;
}
};
b、需要初始化const修饰的类成员或初始化引用成员数据;
//对象引用或者cosnt修饰的数据成员
class Test13 {
private:
const int a;
int &b;
public:
Test13(int &b) : a(10), b(b) {
}
};
c、子类初始化父类的私有成员;
//子类初始化父类的私有成员,需要在(并且也只能在)参数初始化列表中显示调用父类的构造函数
class Test14{
public:
Test14(){};
Test14(int x){
this->x=x;
}
void show(){
cout <<x << endl;
}
private:
int x;
};
class Test144: public Test14{
public:
Test144():Test14(123){
}
};
int main() {
Test14 *p=new Test144();
p->show();
return 0;
}
2,效率的原因需要这样使用
类对象的构造顺序显示,进入构造函数体后,进行的是计算,是对成员变量的赋值操作,显然,赋值和初始化是不同的,这样就体现出了效率差异,如果不用成员初始化类表,那么类对自己的类成员分别进行的是一次隐式的默认构造函数的调用,和一次赋值操作符的调用,如果是类对象,这样做效率就得不到保障
总结:C++ 中提供了初始化列表对成员变量进行初始化,其语法规则为
a,代码示例:
1 ClassName::ClassName() : m1(v1), m2(v1, v2), m3(v3) // 用 v1, (v1, v2), v3 分别对 m1, m2, m3 初始化;
2 {
3 // some other initialize operation;
4 }
1,初始化列表应该在构造函数的地方使用;
2,构造函数参数列表之后函数体之前定义初始化列表;
3,其作用就是对成员变量进行初始化;
b,注意事项(避免 bug 很重要):
1,成员的初始化顺序与成员的声明顺序相同;
2,成员的初始化顺序与初始化列表中的位置无关;
3,初始化列表先于构造函数的函数体执行;
(1)当构造函数的函数体开始执行的时候,对象已经创建完毕了,执行构造函数的函数体仅仅是为了初始化我们这个对象的状态而已;
(2)所以说初始化列表既然是用于初始化,那么必须在我们这个类对象创建的同时来进行执行,而不应该是对象已经创建好了才来进行一系列的初始化工作,这一点是有明确差异的,这个差异也就是初始化和赋值之间的差异;
14.C++在栈上创建对象和在堆上创建对象的区别
- 在栈上常见对象是静态建立一个类对象(A a; //在栈中分配内存),是由编译器为对象在栈空间中分配内存,通过直接移动栈顶指针挪出适当的空间,然后在这片内存空间上调用构造函数形成一个栈对象。栈是由编译器自动分配释放 ,存放函数的参数值,局部变量的值,对象的引用地址等。其操作方式类似于数据结构中的栈,通常都是被调用时处于存储空间中,调用完毕立即释放
- 在堆上创建对象是动态建立类对象(A* p=new A(),Ap=(A)malloc()),是使用new运算符将对象建立在堆空间中,在栈中只保留了指向该对象的指针,堆中通常保存程序运行时动态创建的对象,C++堆中存放的对象需要由程序员分配释放,它存在程序运行的整个生命期,直到程序结束由OS释放
class A {
private:
vector<string> v1;
};
A a; // v1 分配在栈上,但是不保证 vector 或 string 的内部容器分配在栈上。
A * a = new A; // v1 分配在堆上,假定没重载 new 操作符。
A * a = (A *)malloc(sizeof(A)); // v1 分配在堆上。
class A {
public :
A(){ p = malloc(1000);}
private:
void * p;
};
A a; // a 分配在栈上,变量 p 也分配在栈上,但是 p 指向的内存空间分配在堆上。
14.C++中的友元函数
-
1、为什么要引入友元函数:在实现类之间数据共享时,减少系统开销,提高效率
具体来说:为了使其他类的成员函数直接访问该类的私有变量
即:允许外面的类或函数去访问类的私有变量和保护变量,从而使两个类共享同一函数
优点:能够提高效率,表达简单、清晰
缺点:友元函数破环了封装机制,尽量不使用成员函数,除非不得已的情况下才使用友元函数。 -
2、什么时候使用友元函数:
1)运算符重载的某些场合需要使用友元。
2)两个类要共享数据的时候 -
3、怎么使用友元函数:
友元函数的参数:
因为友元函数没有this指针,则参数要有三种情况:
1、 要访问非static成员时,需要对象做参数;–常用(友元函数常含有参数)
2、 要访问static成员或全局变量时,则不需要对象做参数
3、 如果做参数的对象是全局对象,则不需要对象做参数
class FirendA {
private:
int a;
public:
FirendA() {
a = 100;
}
friend void fun1(const FirendA &a);//声明友元函数
};
void fun1(const FirendA &a) {
//因为是FirendA的友元函数,可以访问所有FirendA的成员
cout << a.a << endl;
}
int main() {
FirendA aa;
fun1(aa);
return 0;
}
15.C++中的运算符重载
重载的运算符是带有特殊名称的函数,函数名是由关键字 operator 和其后要重载的运算符符号构成的。与其他函数一样,重载运算符有一个返回类型和一个参数列表。
A operator+(const A&);
/// 运算符重载
class Box {
private:
int width;
int height;
int length;
public:
double getVolume(void) {
return length * width * height;
}
int getWidth() const {
return width;
}
void setWidth(int width) {
Box::width = width;
}
int getHeight() const {
return height;
}
void setHeight(int height) {
Box::height = height;
}
int getLength() const {
return length;
}
void setLength(int length) {
Box::length = length;
}
Box operator+(const Box &b) {
Box box;
box.width = this->width + b.width;
box.height = this->height + b.height;
box.length = this->length + b.length;
return box;
}
void printf() {
cout << "width:" + width << "height:" + height << "length:" + length << endl;
}
};
int main() {
Box box1;
Box box2;
Box box3;
box1.setLength(1);
box1.setWidth(2);
box1.setHeight(3);
box2.setLength(2);
box2.setWidth(3);
box2.setHeight(4);
cout << "Volume of Box1 : " << box1.getVolume() << endl;
cout << "Volume of Box2: " << box2.getVolume() << endl;
box3 = box1 + box2;
cout << "Volume of Box3: " << box3.getVolume() << endl;
return 0;
}
不可以重载的运算符
.:成员访问运算符
., ->:成员指针访问运算符
:::域运算符
sizeof:长度运算符
?::条件运算符
#: 预处理符号
16.C++基类调用子类的protected或者private函数
- protected
基类的成员函数和子类的成员函数能调用基类的protected成员;
基类的对象、子类的对象,均不能调用基类的protected成员; - private
基类的成员函数能调用自己的private成员;
子类的成员函数不能调用基类的private成员;
///c++ 访问权限 :继承 默认是private
class Base {
private:
int a;
protected:
int b;
public:
int c;
void test() {
cout << "我是base" << endl;
}
};
class Parent : public Base {
public:
void test2(Base &base) {
base.c;
this->b;//访问protected成员需要用this
base.test();
}
};
///protected
//基类的成员函数和子类的成员函数能调用基类的protected成员;
//基类的对象、子类的对象,均不能调用基类的protected成员;
///private
//基类的成员函数能调用自己的private成员;
//子类的成员函数不能调用基类的private成员;
int main() {
Parent parent;
parent.c;
parent.test();
Parent *parent1;
parent1->test();
return 0;
}
17.C++中的 Virtual 虚函数
虚函数是指一个类中你希望重载的成员函数 ,当你用一个 基类指针或引用 指向一个继承类对象的时候,调用一个虚函数时, 实际调用的是继承类的版本
class Base {
private:
int a;
protected:
int b;
public:
int c;
virtual void test() {
cout << "我是base" << endl;
}
};
class Parent : public Base {
public:
void test2(Base &base) {
base.c;
this->b;
base.test();
}
virtual void test() {
cout << "我是parent" << endl;
}
};
void fun2(Base &base) {
base.test();
}
///virtual 多态 虚函数
int main() {
Parent parent;
Base base;
fun2(parent); //需要加上 virtual 才能调用到Parent的test方法
fun2(base);
return 0;
}