c和c++ 几个要害不同

这一小节 主要讲述一下,c言语和c++言语 几个要害的不同,体会一下c++ 相比与c言语 做了哪些改进。 有了c的基础 学习c++ 会快不少

c言语中的字符问题

Android JNI 编程 - C++基础 (四)

c2报错,应该很简略理解了, 字符 只能和 ” 匹配 slash2 报错,由于c言语中字符串有必要是 ‘\0’ 完毕 ,而单引号显然没有 \0 这个东西,

所以你看c言语中你要写个字符串和字符是真得小心。

c++ 中有了string 今后 就便利很多了

char c1 = 'yes';
cout << c1 << endl;
string s1(1, 'yes');
cout << s1 << endl;
string s2(3, 'yes');
cout << s2 << endl;
string s3("yes");
cout << s3 << endl;

看下输出

Android JNI 编程 - C++基础 (四)

咱们总算不必关怀究竟应该用”仍是””了。

数组退化问题

先看一下 下面的代码:


#include <iostream>
using namespace std;
double average1(int arr[5]) {
  double result = 0;
  int length = sizeof(arr) / sizeof(arr[0]);
  cout << "average1-length:" << length << endl;
  for (int i = 0; i < length; ++i) {
    result += arr[i];
  }
  return result / length;
}
int main() {
  int array[] = {1, 2, 3, 4, 5};
  // 求数组长度的技巧
  int length = sizeof(array) / sizeof(array[0]);
  cout << "length:" << length << endl;
  cout << "average1:" << average1(array) << endl;
  return 0;
}

一个很简略的求平均值的程序

Android JNI 编程 - C++基础 (四)

看下执行成果,可能不熟悉c言语的人都懵逼了, 为啥函数内部的数组的长度计算出来的值是2?

如果你用新版本的ide的话会看到提示:

Android JNI 编程 - C++基础 (四)

讲白了,关于c言语来说, 数组作为参数来传递的时分, 实践上这个函数的参数 就变成了 数组的指针

关于64位的电脑来说,数组的指针便是巨细便是8,而一个int值巨细是4 那自然算出来便是2了。

也便是说 上面的函数等同于

double average1(int* arr) {
  double result = 0;
  int length = sizeof(arr) / sizeof(arr[0]);
  cout << "average1-length:" << length << endl;
  for (int i = 0; i < length; ++i) {
    result += arr[i];
  }
  return result / length;
}

在C言语中,数组名常常被解说为指向数组第一个元素的指针。由于C言语中指针是一种特殊的变量类型,所以数组名也能够被视为指向一个特定类型的指针。

但是,当咱们将一个数组作为参数传递给函数时,数组名就会产生一种奇怪的“退化”现象,即它不再是指向数组的指针,而是成为了一个一般的指针。

这种现象的原因是当数组作为函数参数时,它会自动转化为指向其第一个元素的指针。因此,在函数中运用数组名时,它实践上是一个指向数组第一个元素的指针,而不是一个真正的数组。

这种“退化”现象可能会导致一些问题,特别是当咱们企图确定数组的长度时。由于在函数中,咱们只能看到指针,而不能看到指针所指向的数组的巨细。

因此,如果咱们想在函数中运用一个数组,并需求知道其巨细,咱们有必要将数组的巨细作为另一个参数传递给函数。

c言语这么做主要是为了功率考虑,毕竟如果是值传递的话 涉及到数组的复制 那功率就太低了

关于c言语来说 能够这么改(其实便是让调用者把数组的实践长度传递进去就能够了):

double average2(int* arr,int length) {
  double result = 0;
  cout << "average1-length:" << length << endl;
  for (int i = 0; i < length; ++i) {
    result += arr[i];
  }
  return result / length;
}

c++ 怎么处理这个问题的?

首要改一下cmake文件

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")

然后用stl库的方法来处理就行了

vector<int> myArray {1, 2, 3, 4, 5};
cout << "average3:" << average3(myArray) << endl;
double average3(vector<int> &v) {
  double result = 0;
  auto it = v.begin();
  for (; it != v.end(); ++it) {
    result += *it;
  }
  return result / v.size();
}

考虑一个问题,如何完成一个动态长度的数组?关于c言语来说,必定要用到malloc和free,很费事,而在c++中咱们很简略用vector就能够完成了 这儿就不继续写了,大家有个印象即可

算术右移和逻辑右移

先看一段代码:

int main() {
  char a1=0x63;//0110 0011
  a1=(a1<<4); //0011 0000
  printf("0x%x \n",a1);
   a1=0x63;//0110 0011
  a1=(a1>>4); //0000 0110
  printf("0x%x \n",a1);
  char a2=0x95;//1001 0101
  a2=(a2<<4); //0101 0000
  printf("0x%x \n",a2);
  a2=0x95;// 1001 0101
  a2=(a2>>4); // 以为是0000 1001 成果实践是 1111 1001 管用右移 逻辑右移?
  printf("0x%x \n",a2); //为啥是0xfffffff9 ?
  return 0;
}

有问题的程序实践在这:

Android JNI 编程 - C++基础 (四)

这个和咱们幻想中的 0000 1001的成果并不一样

实践上在c言语中

右移操作符分为算术右移和逻辑右移。

逻辑移位:左面用0填充,右边丢弃

算术移位:左面用原该值的符号位填充,右边丢弃

但常见的编译器是管用右移

要处理上面那个问题 用 无符号数来表明即可

unsigned char a2=0x95;//1001 0101

看下成果:

Android JNI 编程 - C++基础 (四)

所以这儿c言语就有带点坑了,关于c言语来说,咱们运用位移运算符的时分 要考虑上下文的环境 究竟是管用右移动仍是逻辑右移, 究竟要不要用无符号数

c++的处理方案 bitset

int main() {
  bitset<8> bt=0x95;
  cout<<bt<<endl;
  bt=(bt>>4);
  cout<<bt<<endl;
  cout<<"--------------------"<<endl;
  //c++ 就简略的多,用bitset要言不烦 完成一个简略的权限判别
  bitset<10> priv=0xFF;
  bitset<10> P_BACKUP=(1<<6);
  bitset<10> P_ADMIN=(1<<7);
  cout<<priv<<endl;
  cout<<P_BACKUP<<endl;
  cout<<P_ADMIN<<endl;
  if ((priv & P_BACKUP)==P_BACKUP){
    cout<<"backup"<<endl;
  }
  if ((priv & P_ADMIN)==P_ADMIN){
    cout<<"admin"<<endl;
  }
  return 0;
}

看下执行成果:

Android JNI 编程 - C++基础 (四)

强制类型转化

看下面这个简略的代码:

int main() {
  int array[] = {1, 2, 3};
  cout << sizeof(array) / sizeof(array[0]) << endl;
  int threshold = -1;
  if (sizeof(array) / sizeof(array[0]) > threshold) {
    cout << "合法数组" << endl;
  } else {
    cout << "不合法数组" << endl;
  }
  return 0;
}

应该每个人看到了 都觉得这个当地肯定会打印合法数组 对吧

但实践上跑一下你就知道

Android JNI 编程 - C++基础 (四)

竟然是不合法的? 这是为啥?

其实是由于 sizeof 的回来值是一个无符号类型的, 当你用上述的写法的时分 threshold这个值-1 会自动转化为 有符号类型的,

cout << (unsigned int) threshold<< endl;

这个打印一下出来的值 是 4294967295,真相大白了

所以这个当地非常坑,处理方案, 把 无符号转成有符号 ,然后再比较即可

int array[] = {1, 2, 3};
cout << sizeof(array) / sizeof(array[0]) << endl;
int threshold = -1;
int res = sizeof(array) / sizeof(array[0]);
if (res > threshold) {
  cout << "合法数组" << endl;
} else {
  cout << "不合法数组" << endl;
}

c言语中的强制类型转化 躲藏的比较深,很简略犯错,而且这种错很难排查 ,由于你很难一眼看出来 我究竟哪里做了类型转化?

c++ 怎么处理的?

c++ 其实便是用了一些新的东西 xxx_cast 的要害字 来显现的告诉编程者 这儿做了类型转化,转化成了哪种类型

例如:

double a= static_cast<double>(1);

在c++中,处理大数的运算 一般都是用boost库,大家了解一下即可。

c言语中的字符串缺点

int main() {
  char str1[] = "string";
  cout << strlen(str1) << endl; //6
  cout << sizeof(str1) / sizeof(str1[0]) << endl;//7
  char str2[]="stri\0ng";
  cout << strlen(str2) << endl; //4
  cout << sizeof(str2) / sizeof(str2[0]) << endl;//8
  return 0;
}

输出如下:

Android JNI 编程 - C++基础 (四)

这个输出成果不必解说了吧,讲白了,strlen计算长度是计算的 \0 之前的长度,

char str1A[30]="stringA";
// strcat 这个函数的第一个参数的长度 必定要足够包容 拼接后的长度
strcat(str1A,str2);
cout << str1A << endl; //stringAstri
cout << strlen(str1A) << endl; //11
cout << sizeof(str1A) / sizeof(str1A[0]) << endl;//30

c++中的处理方案

string sstr1="string";
cout << sstr1 << endl; //stringAstri
cout << sstr1.length() << endl; //6
cout << sizeof(sstr1) << endl; //8
cout << sstr1.capacity() << endl; //6
cout << sstr1.size() << endl; //6
string sstr2="stri\0ng";
cout << sstr2 << endl; //stringAstri
cout << sstr2.length() << endl; //4
cout << sizeof(sstr2) << endl; //8
cout << sstr2.capacity() << endl; //4
cout << sstr2.size() << endl; //4
sstr1 += sstr2;
cout << sstr1 << endl; //stringAstri
cout << sstr1.length() << endl; //10
cout << sizeof(sstr1) << endl; //8
cout << sstr1.capacity() << endl; //12
cout << sstr1.size() << endl; //10

c++中 字符串的写法 比c言语中要便利不少, 但是要注意几点,length回来的依然是字符串实践的长度,capactity是实践容量的巨细,跟扩容相关

这儿提一下,有兴趣的能够看下redis的字符串完成,功率上比这儿原生的要高不少。

字符串的不行变性

int main() {
  char array[]="helloworld";
  array[2]='c';
  cout<<array[2]<<endl;
  char* str="helloworld";
   str[2]='c'; //interrupted by signal 11: SIGSEGV)
   cout<<str<<endl;
  return 0;
}

上述代码中的第二段 执行起来会报错, 这儿就涉及到一个字符串的不行变性问题了,其实和java是有相似性的,大家自行体会一下即可。 不要犯错就行

C++中的指针

简略混淆的数组的指针和指针的数组

int main() {
  // T* t[] 指针的数组   int* a[] 里边的值 都是指针
  // T (*t)[] 数组的指针 int (*a)[]  []优先级比较高
  int c[3] = {1, 2, 3};
  int *a[3]; // a代表一个长度为3的数组,元素是指针
  int (*b)[3]; // 代表一个数组长度为3的数组的指针
  b = &c; // b指向c
  // 下面是将c中的每一个元素的地址赋值给a
  for (int i = 0; i < 3; ++i) {
    a[i] = &c[i];
  }
  cout << "a[0]:" << *(a[0]) <<"-------"<< a[0] << endl;
  cout << (*b)[0] <<"------------"<< &((*b)[0]) << endl;
  return 0;
}

Android JNI 编程 - C++基础 (四)

const 与指针

Android JNI 编程 - C++基础 (四)

const 在 最左面也是一样的规矩

Android JNI 编程 - C++基础 (四)

指向指针的指针

int main() {
  int a=123;
  // *操作符具有从右向左的结合性
  int* b=&a;
  // *(*c)
  int** c=&b;
  return 0;
}

野指针

这儿要注意了,多数java程序员 刚开始写c的时分 很简略犯下面这个过错

Android JNI 编程 - C++基础 (四)

原因就在于,很多javaer 会误以为 这儿执行的时分会报错,至少是个空指针之类的

但是并没有

这段程序编译是ok的,甚至运转起来也是ok的( 严格来说运转是否ok 要看命运)

这儿本质上界说了一个int 类型的指针, 但是你没有给这个指针赋予一个地址

在没有地址的情况下,你给他写入了一个值。

这个就很风险了,由于运转的时分 鬼才知道a的地址会被随机分配到哪里。。。

所以千万不要这么写!!!!!

cpp中是需求你手动给一个指针设置null的

a= NULL;

手动设置为null今后 运转起来就会犯错了

Android JNI 编程 - C++基础 (四)

这儿跟java 是不同的,java你 界说一个String a,默认就指向null

**别的要注意的是 delete和free 之后的指针 必定要记得设置为null,不然很简略成为野指针 **

**没有初始化的,不必的 或许超出范围的指针 必定要设置为null **

这儿最难的便是判别一个指针的效果范围,只能靠多写 多找感觉

内存走漏

c++ 没有什么gc的概念,需求你自己手动管理堆区的内存

int main() {
  int *c = new int(1000);
  if (c != NULL) {
    delete c;
    c = NULL;
  }
  int* arr=new int[1000];
  if (arr != NULL) {
    // 新手比较简略忘记 数组也需求开释 数组的开释要加[]
    delete[] arr;
    arr = NULL;
  }
  // 其他目标的delete就不演示了
  return 0;
}

智能指针 auto_ptr

这个东西在高版本的c++代码中 会被移除, 原因是 他有个坑

假设你用ap1 去指向一块内存区域b 别的一个人用ap2 也指向内存区域b 当ap2 指向内存区域b的时分,ap1 自动就指向null了。。。。

这和java的很不一样吧。

运用这货 必定要注意

int main() {
  // 对int 运用  不需求你delete操作,在脱离效果域时自动开释
  auto_ptr<int> p(new int(10));
  cout << *p << endl;
  // 对数组运用
  auto_ptr<string> lan[5]={
      auto_ptr<string>(new string("C++")),
    auto_ptr<string>(new string("Java")),
    auto_ptr<string>(new string("C#")),
    auto_ptr<string>(new string("Python")),
    auto_ptr<string>(new string("Ruby"))
  };
  auto_ptr<string> pC;
  // 这儿就有坑了,你不能这样做,由于你的pC指向的是lan[1]的内存,而lan[1]的内存是被开释的
  pC = lan[1];
  // 这儿是ok的 会打印java
  cout << *pC << endl;
  for (int i = 0; i < 5; ++i) {
    // 但是这儿就坑了,当i=1的时分 程序就会报错了
    cout << *lan[i] << endl;
  }
  return 0;
}

智能指针 unique_ptr

他只能被一个目标持有,不支撑复制和赋值

尽管不能复制,但是能够支撑 搬运所有权。

下面看个比如

这儿显然就会报错了:

Android JNI 编程 - C++基础 (四)

搬运所有权便是能够的:

int main() {
  auto w = make_unique<int>(10);
  cout << *(w.get()) << endl;
  auto w2 =std::move(w);
  cout << *(w2.get()) << endl;
  cout << *(w.get()) << endl; // 这行会报错,由于w现已被移动了
  return 0;
}

智能指针 shared_ptr

这个跟java中就有一点点像了

是经过引证计数共享一个目标

当引证计数为0时,这个目标没有被运用,就能够进行析构了。 是不是很像java中的引证计数法?

一起呢,循环引证也会在这儿呈现了。这儿的概念 javaer 应该都很熟悉了

一起,还有一个weak_ptr 与这个shared_ptr 共同工作。 也很像java中的弱引证

来看几个比如

int main() {
  {
    auto wA = shared_ptr<int>(new int(20));
    {
      auto wA2 = wA;
      cout << *wA2.get() << endl;//20
      cout << *wA.get() << endl;//20
      cout << wA2.use_count() << endl;//2
      cout << wA.use_count() << endl;//2
    }
    // 下面这个输出1 是由于 wA2 现已出了大括号了,效果域完毕了,所以 wA 的引证计数为1
    cout << wA.use_count() << endl;//1
  }
  return 0;
}

再看一个比如

#include <iostream>
#include <memory>
using namespace std;
// 声明
struct B;
struct A {
  shared_ptr<B> pb;
  // 析构函数, 用来阐明被开释掉
  ~A() {
    cout << "A::~A()" << endl;
  }
};
struct B {
  shared_ptr<A> pa;
  ~B() {
    cout << "B::~B()" << endl;
  }
};
struct BW;
struct AW {
  shared_ptr<BW> pb;
  // 析构函数, 用来阐明被开释掉
  ~AW() {
    cout << "AW::~AW()" << endl;
  }
};
struct BW {
  weak_ptr<AW> pa;
  ~BW() {
    cout << "BW::~BW()" << endl;
  }
};
void Test() {
  shared_ptr<A> tA(new A());
  shared_ptr<B> tB(new B());
  cout << "tA.use_count():" << tA.use_count() << endl;
  cout << "tB.use_count():" << tB.use_count() << endl;
  tA->pb = tB;
  tB->pa = tA;
  cout << "tA.use_count():" << tA.use_count() << endl;
  cout << "tB.use_count():" << tB.use_count() << endl;
}
void Test2() {
  shared_ptr<AW> tAw(new AW());
  shared_ptr<BW> tBw(new BW());
  cout << "tAw.use_count():" << tAw.use_count() << endl;
  cout << "tBw.use_count():" << tBw.use_count() << endl;
  tAw->pb = tBw;
  tBw->pa = tAw;
  cout << "tAw.use_count():" << tAw.use_count() << endl;
  cout << "tBw.use_count():" << tBw.use_count() << endl;
}
int main() {
 Test();
 Test2();
  return 0;
}

看下输出:

Android JNI 编程 - C++基础 (四)

这儿要注意2点,

tAw的 引证计数是1的原因是 tbW是以weak的形式去持有他的

第二个要注意的便是 Test函数产生了内存走漏 由于没有走 析构函数

引证

引证在c++中便是一个 不允许修改的指针

对我这种新手来说,能用引证就不要用指针了,指针稍微不注意 就简略埋坑

引证能够认为是指定变量的别号,运用时能够认为是变量本身

引证的运用比较简略 这儿就不展开讲了。